一直木有看过这个细节,用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. win下apache的error.log和access.log文件过大

    在httpd.conf中修改ErrorLog和CustomLog的配置 ErrorLog "|E:/apache2.2/bin/rotatelogs.exe E:/apache2.2/log ...

  2. Android开发日常-listVIiew嵌套webView回显阅读位置

     详情页布局结构 需求是回显webview展示网页的阅读位置 方案1: 使用webview.getScrollY()获取滑动到的位置,用setScrollY()回显设置, 但是两个方法都出现了问题,g ...

  3. 在windows7中配置ant环境变量

    假设本地以及安装好JDK并且配置好环境变量. 新建/修改系统环境变量 ANT_HOME   D:\develop\apache-ant-1.10.3 PATH       %ANT_HOME%\bin ...

  4. c# 24种设计模式

    备忘录模式(Memento Pattern) 策略模式(Strategy Pattern) 抽象工厂模式(Abstract Factory Pattern) 代理模式(Proxy Pattern) 单 ...

  5. 4K - 找新朋友

    新年快到了,“猪头帮协会”准备搞一个聚会,已经知道现有会员N人,把会员从1到N编号,其中会长的号码是N号,凡是和会长是老朋友的,那么该会员的号码肯定和N有大于1的公约数,否则都是新朋友,现在会长想知道 ...

  6. Loadrunner使用键盘快捷键

    ---------Loadrunner使用键盘快捷键------------ alt+f8 比较当前快照(仅限于 Web Vuser) alt+ins 新建步骤 ctrl+a 全选 ctrl+c 复制 ...

  7. Java的反射技术

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

  8. Ubuntu 配置双网卡的问题

    一台双网卡电脑拥有两个网关是不可能的,因为默认网关(default gateway)只能是一个.给Ubuntu Linux服务器安装两块网卡,分别设置不同的ip和网关(内网和外网),外网的通过外网网卡 ...

  9. How to use external classes and PHP files in Laravel Controller?

    By: Povilas Korop Laravel is an MVC framework with its own folder structure, but sometimes we want t ...

  10. QueryRunner类的八种结果处理集

    package cn.jy.demo; import java.sql.Connection; import java.sql.SQLException; import java.util.List; ...