一直木有看过这个细节,用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:
了。也就是这样的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//假设有一个用户实体类
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
上还是会出错的。
对上面的代码做一次小小的改进:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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)
解码。
在大体逻辑不修改的条件下,我们看下完整的可以存实体类对象的代码。
1
2
3
4
5
6
7
8
|
//然后
let
userModel
=
WeiboUserModel
()
//正式开始
let
userDefaults
=
NSUserDefaults
.
standardUserDefaults
()
let
encodedObject
=
NSKeyedArchiver
.
archivedDataWithRootObject
(
object
)
userDefaults
.
setObject
(
encodedObject
,
forKey
:
"UserInfoKey"
)
userDefaults
.
synchronize
()
//最后不要忘了这个
|
这样就可以运行了。但是我们不能止步于此。因为如果项目中需要保存的地方太多的时候,到处都写满了(极有可能是复制粘贴)NSUserDefaults
实例的调用。这样的代码太过僵化。而且很容易忘记最后的userDefaults.synchronize ()
调用。这会导致对象的存储出问题。
所以我们要对这一部分的代码做一定的封装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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对象没有存上了。来看看调用的一个小细节。
1
2
3
|
userDefaults
.
saveCustomObject
(
customObject
:
userModel
,
key
:
"UserInfoKey"
)
//存
userDefaults
.
getCustomObject
(
"UserInfoKey"
)
as
?
WeiboUserModel
//取
|
好的,到这。完整项目的代码在这里
to be continued
本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/5156411.html,如需转载请自行联系原作者