Swift: 用UserDefaults保存复杂对象

前端之家收集整理的这篇文章主要介绍了Swift: 用UserDefaults保存复杂对象前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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

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

  1. 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. class UserModel {
  3. var userId: String = ""
  4. var accessToken: String = ""
  5. }
  6.  
  7. //然后
  8. let userModel = UserModel()
  9.  
  10. //正式开始
  11. let userDefaults = NSUserDefaults.standardUserDefaults()
  12. let encodedObject = NSKeyedArchiver.archivedDataWithRootObject(object)
  13. userDefaults.setObject(encodedObject,forKey: "UserInfoKey")
  14. userDefaults.synchronize() //最后不要忘了这个

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

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

如此的修改就可以让他们跑起来了。下面依次解释:
1. 实现NSObjectNSCodingNSObject可以不加,用@objc修饰某些方法也可以。NSCoding接口提供了序列化和反序列化对象的时候的编解码方法UserModel的类名称修改WeiboUserModel。这部分代码是整个项目的一部分,后面会补齐。
2. 在序列化一个对象的时候使用方法func encodeWithCoder(aCoder: NSCoder)编码。
3. 反序列化的时候用方法init?(coder aDecoder: NSCoder)解码。

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

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

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

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

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

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

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

好的,到这。完整项目的代码请移步这里

to be continued

猜你在找的Swift相关文章