zoukankan      html  css  js  c++  java
  • 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:了。也就是这样的用法:

    //假设有一个用户实体类
    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上还是会出错的。

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

    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)解码。

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

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

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

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

    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对象没有存上了。来看看调用的一个小细节。

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

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

    to be continued
     
  • 相关阅读:
    Windows性能计数器应用
    Azure Oracle Linux VNC 配置
    Azure 配置管理系列 Oracle Linux (PART6)
    Azure 配置管理系列 Oracle Linux (PART5)
    Azure 配置管理系列 Oracle Linux (PART4)
    Azure 配置管理系列 Oracle Linux (PART3)
    Azure 配置管理系列 Oracle Linux (PART2)
    vagrant多节点配置
    docker基本操作
    LINUX开启允许对外访问的网络端口命令
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/5156411.html
Copyright © 2011-2022 走看看