一直木有看过这个细节,用UserDefaults是能不能存复杂一点的对象。大家可能都看到过UserDefaults的一个方法setObject: forKey:,用这个方法存过NSDictionaryNSArray什么的,也存过字符串。

偶然一次直接存了一个继承自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:方法可以存NSDataNSString什么的对象,即使是NSDictionaryNSArray内存放的元素也必须是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。这个时候就要用到NSKeyedArchiverNSKeyedUnarchiver,这也就间接的用到了NSCoding接口。因为一个实体类如果没有实现NSCoding那么在NSKeyedArchiverNSKeyedUnarchiver上还是会出错的。

对上面的代码做一次小小的改进:

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. 实现NSObjectNSCodingNSObject可以不加,用@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保存复杂对象的更多相关文章

  1. NSUserDefault 保存自定义对象

    由于NSUserDefaults 不支持保存自定类,保存的对象需要实现NSCoding协议,不过自定的类型就算实现了NSCoding也不可以保存,可以通过以下方法实现: //h文件 #import & ...

  2. Tomcat关闭后,重新启动,session中保存的对象为什么还存在解决方法

    Tomcat关闭后,重新启动,session中保存的对象为什么还存在各们朋友大家好:         当我关闭Tomcat,重新启动后,session中保存的对象还依然存在,仍然可以使用,不知这是什么 ...

  3. Adobe AIR and Flex - 保存序列化对象文件(译)

    创建任何桌面应用程序几乎总是需要在本地存储数据,通过Adobe AIR我们有几下面几个选择,一个是我们能够使用内置的 SQLite 数据库支持,对于少量的数据这是大材小用了.另外一个选择是我们通过把数 ...

  4. 在MySQL中保存Java对象

    需要在MySQL中保存Java对象. 说明: 对象必须实现序列化 MySQL中对应字段设置为blob 将Java对象序列化为byte[] public static byte[] obj2byte(O ...

  5. Map集合的遍历方式以及TreeMap集合保存自定义对象实现比较的Comparable和Comparator两种方式

    Map集合的特点 1.Map集合中保存的都是键值对,键和值是一一对应的 2.一个映射不能包含重复的值 3.每个键最多只能映射到一个值上 Map接口和Collection接口的不同 Map是双列集合的根 ...

  6. Core Data使用之一(Swift): 保存

    Core Data 用于永久化数据,它是基于SQLite数据库的保存一门技术. 那么,在Swift中,它是如何实现的呢? 首先,需要新建一个模板,打开工程中的xcdatamodeld文件,点击“Add ...

  7. [Effective JavaScript 笔记]第24条:使用变量保存arguments对象

    迭代器(iterator)是一个可以顺序存取数据集合的对象.其一个典型的API是next方法.该方法获得序列中的下一个值. 迭代器示例 题目:希望编写一个便利的函数,它可以接收任意数量的参数,并为这些 ...

  8. 会话过程保存数据对象cookie和session

    1 cookie是以键值对保存在浏览器端,服务器端可以创建.接收.发送 cookie 信息. request可以接收 cookie, response 可以发送 cookie. 1)cookie 可以 ...

  9. Spring JDBC保存枚举对象含关键字报错原因之一

    报错信息: org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized S ...

随机推荐

  1. 记录css的常用属性

    background-color:背景颜色 color:字体颜色 text-align:标签内容的位置 margin-left:左外边距 font-size:字体大小 font_family:字体格式 ...

  2. 使用SQL语句创建数据库1——创建一个数据库文件和一个日志文件的数据库

    目的:创建一个数据库文件和一个日志文件的数据库 在matser数据库下新建查询,输入的命令如下: USE master——指向当前使用的数据库.创建数据库实际上是向master数据库中增加一条数据库信 ...

  3. vue2.0跨域携带cookie和IE兼容

    /*vue-resource为了跨域获取到cookie做的操作*/ 1vue-resource跨域问题Vue.http.interceptors.push(function(request, next ...

  4. u-boot之怎么实现分区

    启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0中kernel在哪定义,为什么可以直接引用?针对这个问题展开思考最终定位到 ...

  5. laravel框架数据迁移

    迁移就像数据库的版本控制,允许团队简单轻松的编辑并共享应用的数据库表结构,迁移通常和Laravel 的 schema 构建器结对从而可以很容易地构建应用的数据库表结构.如果你曾经告知小组成员需要手动添 ...

  6. filedisk.sys

    i386 amd http://blog.sina.com.cn/s/blog_4fcd1ea30100r19r.html

  7. Java的反射技术

    什么是反射机制 Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能调用它的任意属性和方法.这种动态获取信息以及动态调用对象属性和方法的即使称为J ...

  8. Java中终止线程的三种方法

    终止线程一般建议采用的方法是让线程自行结束,进入Dead(死亡)状态,就是执行完run()方法.即如果想要停止一个线程的执行,就要提供某种方式让线程能够自动结束run()方法的执行.比如设置一个标志来 ...

  9. [SoapUI] 从Map里面不想要的键值对

    def keysToRemoveForBoss = ["RequestIdBmk", "RequestIdTest"] def extraInfoMapForB ...

  10. servlet 高级知识之Filter

    Filter叫做拦截器, 对目标资源拦截,拦截HTTP请求和HTTP响应,本质是对url进行拦截. 与serlvet不同的是, Filter的初始化是随着服务器启动而启动. 在Filter接口中定义了 ...