- 作者: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