Swift: 用UserDefaults保存复杂对象
一直木有看过这个细节,用UserDefaults是能不能存复杂一点的对象。大家可能都看到过UserDefaults的一个方法setObject: forKey:,用这个方法存过NSDictionary,NSArray什么的,也存过字符串。 偶然一次直接存了一个继承自JSONModel的实体类,然后就悲剧了。后来查了下苹果的文档: Thevalue parameter can be only property list objects:NSData,NSString, NSNumber,NSDate,NSArray, or NSDictionary. ForNSArray andNSDictionary objects, their contents must be property list objects. 简单来说就是setObject:forKey:方法可以存NSData,NSString什么的对象,即使是NSDictionary和NSArray内存放的元素也必须是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。这个时候就要用到NSKeyedArchiver和NSKeyedUnarchiver,这也就间接的用到了NSCoding接口。因为一个实体类如果没有实现NSCoding那么在NSKeyedArchiver和NSKeyedUnarchiver上还是会出错的。 对上面的代码做一次小小的改进: 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. 实现NSObject和NSCoding。NSObject可以不加,用@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,如需转载请自行联系原作者