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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容