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

  • 作者: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:对象的序列化和反序列化)