美文网首页
Swift:对象的序列化和反序列化

Swift:对象的序列化和反序列化

作者: 鳗驼螺 | 来源:发表于2015-12-20 19:14 被阅读2240次
    • 作者:Mandarava(鳗驼螺)

    什么是序列化?

    所谓序列化就是将对象转换成可存储可传递的数据格式的过程,反之,反序列化就是将数据还原为对象的过程。比如游戏的存档数据、应用的配置数据、程序与服务器的交互数据等等,就可以通过序列化技术来实现。

    Swift中序列化的实现

    要使某个类可序列化,只需使类声明实现NCoding协义,并实现协义中的一个必要构建器和一个方法,分别对应序列化和反序列化二个过程。

    required init?(coder aDecoder: NSCoder){ } //提供一个解码器解码数据,通过解码数据来初始化类变量
    func encodeWithCoder(aCoder: NSCoder){ } //提供一个编码器编码数据
    

    NSCoder类中提供了编码和解码各种数据类型的方法,使用该对象进行相应数据的编码和解码即可。

    一个具体例子

    假设一个游戏的存档数据,需要保存的游戏状态如下:

        var roleLevel:Int           //主角等级
        var roleHeight:Float        //主角身高
        var roleName:String         //主角姓名
        var rolePosition:CGPoint    //主角所处的坐标
        var roleItems:[String]      //主角拥有的物品名称列表
        var sceneSize:CGSize        //场景大小
    

    把这个类命名为GameData,并继承自NSObject,同时声明它实现NSCoding协义。具体代码如下:

    class GameData: NSObject, NSCoding {
        var roleLevel:Int           //主角等级
        var roleHeight:Float        //主角身高
        var roleName:String         //主角姓名
        var rolePosition:CGPoint    //主角所处的坐标
        var roleItems:[String]      //主角拥有的物品名称列表
        var sceneSize:CGSize        //场景大小
        
        override init() {
            roleLevel=1
            roleHeight=200.0
            roleName="Player"
            rolePosition=CGPointMake(0, 0)
            roleItems=[]
            sceneSize=CGSizeMake(2000, 2000)
            super.init()
        }
    
        required init?(coder aDecoder: NSCoder){
            roleLevel=aDecoder.decodeIntegerForKey("RoleLevel")
            roleHeight=aDecoder.decodeFloatForKey("RoleHeight")
            roleName=aDecoder.decodeObjectForKey("RoleName") as! String
            rolePosition=aDecoder.decodePointForKey("RolePos")
            roleItems=aDecoder.decodeObjectForKey("RoleItems") as! [String]
            sceneSize=aDecoder.decodeSizeForKey("SceneSize")
            super.init()
        }
    
        func encodeWithCoder(aCoder: NSCoder){
            aCoder.encodeInteger(roleLevel, forKey: "RoleLevel")
            aCoder.encodeFloat(roleHeight, forKey: "RoleHeight")
            aCoder.encodeObject(roleName, forKey: "RoleName")
            aCoder.encodePoint(rolePosition, forKey: "RolePos")
            aCoder.encodeObject(roleItems, forKey: "RoleItems")
            aCoder.encodeSize(sceneSize, forKey: "SceneSize")
        }
    }
    

    具体使用时可以这样调用:

    1. 序列化过程
    //序列化:保存GameData对象
    let gameData=GameData()
    gameData.roleName="Mandarava"
    gameData.roleLevel=100
    gameData.rolePosition=CGPointMake(500, 500)
    gameData.roleItems=["曼陀罗", "黑玉断续膏", "含笑半步瘨"]
    gameData.sceneSize=CGSizeMake(5000, 5000)
    let data=NSKeyedArchiver.archivedDataWithRootObject(gameData)
    //最后将data保存到文件:这里将其作为键值对保存到plist中
    let def=NSUserDefaults.standardUserDefaults()
    def.setObject(data, forKey: "GameData")
    def.synchronize()
    
    1. 反序列化过程
    //反序列化:还原GameData对象
    let def=NSUserDefaults.standardUserDefaults()
    if let data=def.objectForKey("GameData") as? NSData{
         if let gameData=NSKeyedUnarchiver.unarchiveObjectWithData(data) as? GameData{
              print("role name: \(gameData.roleName)")
              print("role height: \(gameData.roleHeight)")
              print("role level: \(gameData.roleLevel)")
              print("role pos: \(gameData.rolePosition)")
              print("role items: \(gameData.roleItems)")
              print("scene size: \(gameData.sceneSize)")
          }
    }
    
    1. 运行结果:
    role name: Mandarava
    role height: 200.0
    role level: 100
    role pos: (500.0, 500.0)
    role items: ["曼陀罗", "黑玉断续膏", "含笑半步瘨"]
    scene size: (5000.0, 5000.0)
    

    以上 by 鳗驼螺 2015.12.20

    升级到Swift 3后的一个小坑

    这篇文章是基于Swift 2.x语法写的,在代码升级到Swift 3后,会出现一个小坑,虽然说这个问题一般可能不会遇到,但还是记下来以为备注。
    问题出在NSKeyedArchiver类的encodeObject:forKey这个方法上,这个是Swift 2.x中的命名,在Swift 3后这个方法没有了,取而代之的是encode:forKey:方法,这个方法有很多重载,根据传入的数据类型自动选择相应的重载方法。问题在于,如果你原先使用encodeObject:forKey:来编码IntBool类型的数据,那么在代码升级到Swift3后,这二种情况会变成调用encode(_ intv: Int, forKey key: String)encode(_ boolv: Bool, forKey key: String)的重载,这会造成原来的解码方式失败,如果你只修正解码方法,会造成与以前的版本的数据存在兼容性问题。具体用代码来说明:

    //Swift2.x
    
    //编码
    aCoder.encodeObject(myIntVal, forKey: "myIntKey")
    aCoder.encodeObject(myBoolVal, forKey: "myBoolKey")
    
    //解码
    myIntVal = aDecoder.decodeObject(forKey: "myIntKey") as! Int
    myBoolVal = aDecoder.decodeObject(forKey: "myBoolKey") as! Bool
    

    上面的代码升级到Swift3后变成下面这样:

    //Swift3
    
    //编码
    aCoder.encode(myIntVal, forKey: "myIntKey") //相当于Swift2.x的encodeInt:forKey:
    aCoder.encode(myBoolVal, forKey: "myBoolKey") //相当于Swift2.x的encodeBool:forKey:
    
    //解码
    myIntVal = aDecoder.decodeInt(forKey: "myIntKey") //修正解码方法
    myBoolVal = aDecoder.decodeBool(forKey: "myBoolKey") //修正解码方法
    

    Swift3的代码中需要修正解码方法,因为方法性质变了,修正后看起来似乎没问题,但事实上如果你的App数据是在Swift2.x代码下保存的,然后代码升级到Swift3,此时用Swift3代码去反序列化之前的App数据,如果数据是Int,Bool类型就会出现失败,因为原来的Swift2.x数据用的是encodeObject:forKey:方法编码的,在Swift3下用decodeInt:forKey:decodeBool:forKey:去解码会失败,而需要用decodeObject:forKey:解码再造型回IntBool。但是,如果数据是在Swift3下保存的,这种解码方式又是会失败的。
    所以,正确的解决方法是,保存数据时将Int,Bool类型造型成NSObject对象,这样强制调用encode(_ objv: Any?, forKey key: String)的重载,如下面的代码:

    //Swift3
    
    //编码
    aCoder.encode(myIntVal as NSObject, forKey: "myIntKey") //相当于Swift2.x的encodeObject:forKey:
    aCoder.encode(myBoolVal as NSObject, forKey: "myBoolKey") //相当于Swift2.x的encodeObject:forKey:
    
    //解码
    myIntVal = aDecoder.decodeObject(forKey: "myIntKey") as! Int //仍然用原来的解码方法
    myBoolVal = aDecoder.decodeObject(forKey: "myBoolKey") as! Bool //仍然用原来的解码方法
    

    当然,这样做主要是为了将旧版本的App数据与新版本的数据进行兼容。如果你在Swift2.x时,在编码Int,Bool类型时,直接使用encodeInt:forKey:encodeBool:forKey:来编码的,升级到Swift3后会正确的调用相应的重载方法,而且通常我们也是这样做的,所以以上的情况不太会发生,但可以注意一下。

    以上 by 鳗驼螺 2017.08.09

    相关文章

      网友评论

          本文标题:Swift:对象的序列化和反序列化

          本文链接:https://www.haomeiwen.com/subject/rpyyhttx.html