一直木有看過這個細節,用UserDefaults是能不能存復雜一點的對象。大家可能都看到過UserDefaults的一個方法setObject: forKey:,用這個方法存過NSDictionary,NSArray什么的,也存過字符串。
偶然一次直接存了一個繼承自JSONModel的實體類,然后就悲劇了。后來查了下蘋果的文檔:
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
簡單來說就是setObject:forKey:方法可以存NSData,NSString什么的對象,即使是NSDictionary和NSArray內存放的元素也必須是property list objects的。具體什么是property list object看這里。關於JSONModel可以看這里,還不錯。
既然蘋果的API已經限制到這個地步了再想別的已經玩不出什么花樣了。是的,你可以存文件。不過這里說的還是用UserDefaults嘛。
解決這個問題的核心思想就是把一個對象轉換為NSData,或者說是序列化為NSData。序列化的說法不一定准確但是存在這樣的一個過程,具體的后面再細說。當一個對象可以轉化為NSData了也就適用NSUserDefaults的方法setObject: forKey:了。也就是這樣的用法:
//假設有一個用戶實體類
class UserModel {
var userId: String = ""
var accessToken: String = ""
}
//然后
let userModel = UserModel()
//正式開始
let userDefaults = NSUserDefaults.standardUserDefaults()
let encodedObject = NSKeyedArchiver.archivedDataWithRootObject(object)
userDefaults.setObject(encodedObject, forKey: "UserInfoKey")
userDefaults.synchronize() //最后不要忘了這個
大體的意思在上面的代碼中全部都體現出來了。但是如果運行上面的代碼肯定是會出錯的。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object UserModel for key UserInfoKey'
因為不是property list object所以執行方法setObject:forKey的時候App直接Crash。
這個問題看似就在property list object上了。但是回到什么說的,我們的思路是把這個自定義的實體類的對象轉化為NSData。這個時候就要用到NSKeyedArchiver和NSKeyedUnarchiver,這也就間接的用到了NSCoding接口。因為一個實體類如果沒有實現NSCoding那么在NSKeyedArchiver和NSKeyedUnarchiver上還是會出錯的。
對上面的代碼做一次小小的改進:
class WeiboUserModel: NSObject, NSCoding { //1
struct PropertyKey {
static let userIdKey = "userId"
static let accessTokenKey = "accessToken"
static let expirationDateKey = "expirationDate"
static let refreshTokenKey = "refreshToken"
}
var userId: String?
var accessToken: String?
var expirationDate: NSDate?
var refreshToken: String?
func encodeWithCoder(aCoder: NSCoder) { //2
aCoder.encodeObject(userId, forKey: PropertyKey.userIdKey)
aCoder.encodeObject(accessToken, forKey: PropertyKey.accessTokenKey)
aCoder.encodeObject(expirationDate, forKey: PropertyKey.expirationDateKey)
aCoder.encodeObject(refreshToken, forKey: PropertyKey.refreshTokenKey)
}
required init?(coder aDecoder: NSCoder) { // 3
userId = aDecoder.decodeObjectForKey(PropertyKey.userIdKey) as? String
accessToken = aDecoder.decodeObjectForKey(PropertyKey.accessTokenKey) as? String
expirationDate = aDecoder.decodeObjectForKey(PropertyKey.expirationDateKey) as? NSDate
refreshToken = aDecoder.decodeObjectForKey(PropertyKey.refreshTokenKey) as? String
}
}
如此的修改就可以讓他們跑起來了。下面依次解釋:
1. 實現NSObject和NSCoding。NSObject可以不加,用@objc修飾某些方法也可以。NSCoding接口提供了序列化和反序列化對象的時候的編解碼方法。
UserModel的類名稱修改 為WeiboUserModel。這部分代碼是整個項目的一部分,后面會補齊。
2. 在序列化一個對象的時候使用方法func encodeWithCoder(aCoder: NSCoder)編碼。
3. 反序列化的時候用方法init?(coder aDecoder: NSCoder)解碼。
在大體邏輯不修改的條件下,我們看下完整的可以存實體類對象的代碼。
//然后 let userModel = WeiboUserModel() //正式開始 let userDefaults = NSUserDefaults.standardUserDefaults() let encodedObject = NSKeyedArchiver.archivedDataWithRootObject(object) userDefaults.setObject(encodedObject, forKey: "UserInfoKey") userDefaults.synchronize() //最后不要忘了這個
這樣就可以運行了。但是我們不能止步於此。因為如果項目中需要保存的地方太多的時候,到處都寫滿了(極有可能是復制粘貼)NSUserDefaults實例的調用。這樣的代碼太過僵化。而且很容易忘記最后的userDefaults.synchronize ()調用。這會導致對象的存儲出問題。
所以我們要對這一部分的代碼做一定的封裝:
extension NSUserDefaults { //1
func saveCustomObject(customObject object: NSCoding, key: String) { //2
let encodedObject = NSKeyedArchiver.archivedDataWithRootObject(object)
self.setObject(encodedObject, forKey: key)
self.synchronize()
}
func getCustomObject(forKey key: String) -> AnyObject? { //3
let decodedObject = self.objectForKey(key) as? NSData
if let decoded = decodedObject {
let object = NSKeyedUnarchiver.unarchiveObjectWithData(decoded)
return object
}
return nil
}
}
我們把存取的方法都放在NSUserDefaults的擴展里。這樣用戶在使用的時候就可以和使用NSUserDefaults本身的方法一樣的了。而且synchronize()方法也封裝在里面了,再也不用擔心忘記d對象沒有存上了。來看看調用的一個小細節。
userDefaults.saveCustomObject(customObject: userModel, key: "UserInfoKey") //存
userDefaults.getCustomObject("UserInfoKey") as? WeiboUserModel //取
好的,到這。完整項目的代碼在這里
to be continued
