Swift: 用UserDefaults保存复杂对象
一直木有看过这个细节,用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
Swift: 用UserDefaults保存复杂对象的更多相关文章
- NSUserDefault 保存自定义对象
由于NSUserDefaults 不支持保存自定类,保存的对象需要实现NSCoding协议,不过自定的类型就算实现了NSCoding也不可以保存,可以通过以下方法实现: //h文件 #import & ...
- Tomcat关闭后,重新启动,session中保存的对象为什么还存在解决方法
Tomcat关闭后,重新启动,session中保存的对象为什么还存在各们朋友大家好: 当我关闭Tomcat,重新启动后,session中保存的对象还依然存在,仍然可以使用,不知这是什么 ...
- Adobe AIR and Flex - 保存序列化对象文件(译)
创建任何桌面应用程序几乎总是需要在本地存储数据,通过Adobe AIR我们有几下面几个选择,一个是我们能够使用内置的 SQLite 数据库支持,对于少量的数据这是大材小用了.另外一个选择是我们通过把数 ...
- 在MySQL中保存Java对象
需要在MySQL中保存Java对象. 说明: 对象必须实现序列化 MySQL中对应字段设置为blob 将Java对象序列化为byte[] public static byte[] obj2byte(O ...
- Map集合的遍历方式以及TreeMap集合保存自定义对象实现比较的Comparable和Comparator两种方式
Map集合的特点 1.Map集合中保存的都是键值对,键和值是一一对应的 2.一个映射不能包含重复的值 3.每个键最多只能映射到一个值上 Map接口和Collection接口的不同 Map是双列集合的根 ...
- Core Data使用之一(Swift): 保存
Core Data 用于永久化数据,它是基于SQLite数据库的保存一门技术. 那么,在Swift中,它是如何实现的呢? 首先,需要新建一个模板,打开工程中的xcdatamodeld文件,点击“Add ...
- [Effective JavaScript 笔记]第24条:使用变量保存arguments对象
迭代器(iterator)是一个可以顺序存取数据集合的对象.其一个典型的API是next方法.该方法获得序列中的下一个值. 迭代器示例 题目:希望编写一个便利的函数,它可以接收任意数量的参数,并为这些 ...
- 会话过程保存数据对象cookie和session
1 cookie是以键值对保存在浏览器端,服务器端可以创建.接收.发送 cookie 信息. request可以接收 cookie, response 可以发送 cookie. 1)cookie 可以 ...
- Spring JDBC保存枚举对象含关键字报错原因之一
报错信息: org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized S ...
随机推荐
- go流程控制与函数
package main; import ( "fmt" ); func main() { isRun := true; //条件判断 if isRun { fmt.Printf( ...
- visio2013专业版激活密匙
Visio 2013最新产品密钥分享,在安装时可以使用以下密钥: 2NYF6-QG2CY-9F8XC-GWMBW-29VV8 FJ2N7-W8TXC-JB8KB-DCQ7Q-7T7V3 VXX6C-D ...
- spirng中的asm与jdk不兼容<已解决>
转载自:spirng中的asm与jdk不兼容<已解决> 前言 不知道前面对eclipse做了什么,使用maven来创建项目,然后转成web,启动的时候一直报错.我弄了好久,还是无法解决,先 ...
- c语言使用指针对int数组的求和
#include <stdio.h> int sump(int *, int *); int main(void) { , , , , }; printf()); ; } int sump ...
- python 网络下载的三种风格 未完成
import osimport timeimport sys import requests#依序下载POP20_CC = ('CN IN US ID BR PK NG BD RU JP' 'MX P ...
- hdu 2066 ( 最短路) Floyd & Dijkstra & Spfa
http://acm.hdu.edu.cn/showproblem.php?pid=2066 今天复习了一下最短路和最小生成树,发现居然闹了个大笑话-----我居然一直写的是Floyd,但我自己一直以 ...
- 安卓个性化 Button
1.加入属性 android:background="@drawable/btn_selector" 2.drawable 下创建btn_selector.xml <?xml ...
- cesium 中地图发生了平移,放缩,旋转等动作所要执行的动作
1.在canvas上得到鼠标点击的是那个键 <html><head><title>js判断鼠标左.中.右键哪个被点击-柯乐义</title><sc ...
- 找不到或无法加载主类(Could not find or load main class )
在Linux环境下,写了一个简单的java程序,通过javac编译成class文件,然后用java 运行的时候,报了这个错误, 搜了一下,可能是classpath的问题,所以用echo $CLASSP ...
- TCHAR函数查询
https://blog.csdn.net/is2120/article/details/27542927