Swift: 用UserDefaults保存复杂对象

简介:

一直木有看过这个细节,用UserDefaults是能不能存复杂一点的对象。大家可能都看到过UserDefaults的一个方法setObject: forKey:,用这个方法存过NSDictionaryNSArray什么的,也存过字符串。

偶然一次直接存了一个继承自JSONModel的实体类,然后就悲剧了。后来查了下苹果的文档:

The value parameter can be only property list objects: NSData, NSString, 
NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.

简单来说就是setObject:forKey:方法可以存NSDataNSString什么的对象,即使是NSDictionaryNSArray内存放的元素也必须是property list objects的。具体什么是property list object看这里。关于JSONModel可以看这里,还不错。

既然苹果的API已经限制到这个地步了再想别的已经玩不出什么花样了。是的,你可以存文件。不过这里说的还是用UserDefaults嘛。

解决这个问题的核心思想就是把一个对象转换为NSData,或者说是序列化为NSData。序列化的说法不一定准确但是存在这样的一个过程,具体的后面再细说。当一个对象可以转化为NSData了也就适用NSUserDefaults的方法setObject: forKey:了。也就是这样的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//假设有一个用户实体类
class  UserModel  {
     var  userId String  ""
     var  accessToken String  ""
}
 
//然后
let  userModel  UserModel ()
 
//正式开始
let  userDefaults  NSUserDefaults . standardUserDefaults ()
let  encodedObject  NSKeyedArchiver . archivedDataWithRootObject ( object )
userDefaults . setObject ( encodedObject forKey "UserInfoKey" )
userDefaults . synchronize ()  //最后不要忘了这个

大体的意思在上面的代码中全部都体现出来了。但是如果运行上面的代码肯定是会出错的。

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object UserModel for key UserInfoKey'

因为不是property list object所以执行方法setObject:forKey的时候App直接Crash。

这个问题看似就在property list object上了。但是回到什么说的,我们的思路是把这个自定义的实体类的对象转化为NSData。这个时候就要用到NSKeyedArchiverNSKeyedUnarchiver,这也就间接的用到了NSCoding接口。因为一个实体类如果没有实现NSCoding那么在NSKeyedArchiverNSKeyedUnarchiver上还是会出错的。

对上面的代码做一次小小的改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class  WeiboUserModel NSObject NSCoding  //1
     struct  PropertyKey  {
         static  let  userIdKey  "userId"
         static  let  accessTokenKey  "accessToken"
         static  let  expirationDateKey  "expirationDate"
         static  let  refreshTokenKey  "refreshToken"
     }
 
     var  userId String ?
     var  accessToken String ?
     var  expirationDate NSDate ?
     var  refreshToken String ?
 
     func  encodeWithCoder ( aCoder NSCoder ) {   //2
         aCoder . encodeObject ( userId forKey PropertyKey . userIdKey )
         aCoder . encodeObject ( accessToken forKey PropertyKey . accessTokenKey )
         aCoder . encodeObject ( expirationDate forKey PropertyKey . expirationDateKey )
         aCoder . encodeObject ( refreshToken forKey PropertyKey . refreshTokenKey )
     }
 
     required  init ?( coder  aDecoder NSCoder ) {  // 3
         userId  aDecoder . decodeObjectForKey ( PropertyKey . userIdKey as String
         accessToken  aDecoder . decodeObjectForKey ( PropertyKey . accessTokenKey as String
         expirationDate  aDecoder . decodeObjectForKey ( PropertyKey . expirationDateKey as NSDate
         refreshToken  aDecoder . decodeObjectForKey ( PropertyKey . refreshTokenKey as String
     }
}

如此的修改就可以让他们跑起来了。下面依次解释: 
1. 实现NSObjectNSCodingNSObject可以不加,用@objc修饰某些方法也可以。NSCoding接口提供了序列化和反序列化对象的时候的编解码方法。

    UserModel的类名称修改  为WeiboUserModel。这部分代码是整个项目的一部分,后面会补齐。 

  2. 在序列化一个对象的时候使用方法func encodeWithCoder(aCoder: NSCoder)编码。 
3. 反序列化的时候用方法init?(coder aDecoder: NSCoder)解码。

在大体逻辑不修改的条件下,我们看下完整的可以存实体类对象的代码。

1
2
3
4
5
6
7
8
//然后
let  userModel  WeiboUserModel ()
 
//正式开始
let  userDefaults  NSUserDefaults . standardUserDefaults ()
let  encodedObject  NSKeyedArchiver . archivedDataWithRootObject ( object )
userDefaults . setObject ( encodedObject forKey "UserInfoKey" )
userDefaults . synchronize ()  //最后不要忘了这个

这样就可以运行了。但是我们不能止步于此。因为如果项目中需要保存的地方太多的时候,到处都写满了(极有可能是复制粘贴)NSUserDefaults实例的调用。这样的代码太过僵化。而且很容易忘记最后的userDefaults.synchronize ()调用。这会导致对象的存储出问题。

所以我们要对这一部分的代码做一定的封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension  NSUserDefaults  //1
     func  saveCustomObject ( customObject  object NSCoding key String ) {  //2
         let  encodedObject  NSKeyedArchiver . archivedDataWithRootObject ( object )
         self . setObject ( encodedObject forKey key )
         self . synchronize ()
     }
 
     func  getCustomObject ( forKey  key String ) - >  AnyObject ? {  //3
         let  decodedObject  self . objectForKey ( key as NSData
 
         if  let  decoded  decodedObject  {
             let  object  NSKeyedUnarchiver . unarchiveObjectWithData ( decoded )
             return  object
         }
 
         return  nil
     }
}

我们把存取的方法都放在NSUserDefaults的扩展里。这样用户在使用的时候就可以和使用NSUserDefaults本身的方法一样的了。而且synchronize()方法也封装在里面了,再也不用担心忘记d对象没有存上了。来看看调用的一个小细节。

1
2
3
userDefaults . saveCustomObject ( customObject userModel key "UserInfoKey" //存
 
userDefaults . getCustomObject ( "UserInfoKey" as WeiboUserModel  //取

好的,到这。完整项目的代码在这里

to be continued

 

















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/5156411.html,如需转载请自行联系原作者

相关文章
|
Swift
Swift - 如何判断一个对象是否是某个类
Swift - 如何判断一个对象是否是某个类
111 0
|
存储 安全 编译器
Swift-进阶 02:类、对象、属性
Swift-进阶 02:类、对象、属性
332 0
Swift-进阶 02:类、对象、属性
|
Swift 编译器
Swift - 实例对象调用协议方法优先级分析/ witness_methos witness_table分析
本文主要探究: 当一个类遵循了协议,且协议和类都有方法实现时,实例对象调用方法的优先顺序
Swift - 实例对象调用协议方法优先级分析/ witness_methos witness_table分析
《从零开始学Swift》学习笔记(Day 62)——Core Foundation框架之内存托管对象与非托管对象
<span style="font-family:宋体;font-size: 10.5pt; mso-ascii-font-family: Arial; mso-fareast-font-family: 宋体; mso-fareast-theme-font: minor-fareast; mso-hansi-font-family: Arial; mso-bidi-font-family: A
1413 0
|
编译器 Swift
Swift中文教程(五)--对象和类
       Class 类     在Swift中可以用class关键字后跟类名创建一个类。在类里,一个属性的声明写法同一个常量或变量的声明写法一样,除非这个属性是在类的上下文里面,否则,方法和函数的写法也是这样: 1 class Shape { 2 var numberO...
1171 0
|
编译器 Swift
Swift中文教程(五)--对象和类
原文:Swift中文教程(五)--对象和类        Class 类     在Swift中可以用class关键字后跟类名创建一个类。在类里,一个属性的声明写法同一个常量或变量的声明写法一样,除非这个属性是在类的上下文里面,否则,方法和函数的写法也是这样: 1 class Sha...
835 0
|
4月前
|
Unix 调度 Swift
苹果iOS新手开发之Swift 中获取时间戳有哪些方式?
在Swift中获取时间戳有四种常见方式:1) 使用`Date`对象获取秒级或毫秒级时间戳;2) 通过`CFAbsoluteTimeGetCurrent`获取Core Foundation的秒数,需转换为Unix时间戳;3) 使用`DispatchTime.now()`获取纳秒级精度的调度时间点;4) `ProcessInfo`提供设备启动后的秒数,不表示绝对时间。不同方法适用于不同的精度和场景需求。
150 3