- 作者: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")
}
}
具体使用时可以这样调用:
- 序列化过程
//序列化:保存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()
- 反序列化过程
//反序列化:还原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)")
}
}
- 运行结果:
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:
来编码Int
和Bool
类型的数据,那么在代码升级到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:
解码再造型回Int
和Bool
。但是,如果数据是在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
网友评论