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 ...
随机推荐
- 记录css的常用属性
background-color:背景颜色 color:字体颜色 text-align:标签内容的位置 margin-left:左外边距 font-size:字体大小 font_family:字体格式 ...
- 使用SQL语句创建数据库1——创建一个数据库文件和一个日志文件的数据库
目的:创建一个数据库文件和一个日志文件的数据库 在matser数据库下新建查询,输入的命令如下: USE master——指向当前使用的数据库.创建数据库实际上是向master数据库中增加一条数据库信 ...
- vue2.0跨域携带cookie和IE兼容
/*vue-resource为了跨域获取到cookie做的操作*/ 1vue-resource跨域问题Vue.http.interceptors.push(function(request, next ...
- u-boot之怎么实现分区
启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0中kernel在哪定义,为什么可以直接引用?针对这个问题展开思考最终定位到 ...
- laravel框架数据迁移
迁移就像数据库的版本控制,允许团队简单轻松的编辑并共享应用的数据库表结构,迁移通常和Laravel 的 schema 构建器结对从而可以很容易地构建应用的数据库表结构.如果你曾经告知小组成员需要手动添 ...
- filedisk.sys
i386 amd http://blog.sina.com.cn/s/blog_4fcd1ea30100r19r.html
- Java的反射技术
什么是反射机制 Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能调用它的任意属性和方法.这种动态获取信息以及动态调用对象属性和方法的即使称为J ...
- Java中终止线程的三种方法
终止线程一般建议采用的方法是让线程自行结束,进入Dead(死亡)状态,就是执行完run()方法.即如果想要停止一个线程的执行,就要提供某种方式让线程能够自动结束run()方法的执行.比如设置一个标志来 ...
- [SoapUI] 从Map里面不想要的键值对
def keysToRemoveForBoss = ["RequestIdBmk", "RequestIdTest"] def extraInfoMapForB ...
- servlet 高级知识之Filter
Filter叫做拦截器, 对目标资源拦截,拦截HTTP请求和HTTP响应,本质是对url进行拦截. 与serlvet不同的是, Filter的初始化是随着服务器启动而启动. 在Filter接口中定义了 ...