Swift: 用UserDefaults保存复杂对象

简介:

一直木有看过这个细节,用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:了。也就是这样的用法:

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。这个时候就要用到NSKeyedArchiverNSKeyedUnarchiver,这也就间接的用到了NSCoding接口。因为一个实体类如果没有实现NSCoding那么在NSKeyedArchiverNSKeyedUnarchiver上还是会出错的。

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

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. 实现NSObjectNSCodingNSObject可以不加,用@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

 

 
欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | Go:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!http://www.cnblogs.com/sunshine-anycall/p/5156411.html
相关文章
|
安全 编译器 Swift
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
1150 2
|
Unix 调度 Swift
苹果iOS新手开发之Swift 中获取时间戳有哪些方式?
在Swift中获取时间戳有四种常见方式:1) 使用`Date`对象获取秒级或毫秒级时间戳;2) 通过`CFAbsoluteTimeGetCurrent`获取Core Foundation的秒数,需转换为Unix时间戳;3) 使用`DispatchTime.now()`获取纳秒级精度的调度时间点;4) `ProcessInfo`提供设备启动后的秒数,不表示绝对时间。不同方法适用于不同的精度和场景需求。
879 3
|
安全 Swift iOS开发
Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法
本文深入探讨了 Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法。Swift 以其简洁、高效和类型安全的特点,结合 UIKit 丰富的组件和功能,为开发者提供了强大的工具。文章从 Swift 的语法优势、类型安全、编程模型以及与 UIKit 的集成,到 UIKit 的主要组件和功能,再到构建界面的实践技巧和实际案例分析,全面介绍了如何利用这些技术创建高质量的用户界面。
355 2
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户点击按钮时,按钮将从圆形变为椭圆形,颜色从蓝色渐变到绿色;释放按钮时,动画以相反方式恢复。通过UIView的动画方法和弹簧动画效果,实现平滑自然的过渡。
293 1
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
【10月更文挑战第18天】本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户按下按钮时,按钮将从圆形变为椭圆形并从蓝色渐变为绿色;释放按钮时,动画恢复原状。通过UIView的动画方法和弹簧动画效果,实现平滑自然的动画过渡。
245 5
|
存储 移动开发 Swift
使用Swift进行iOS应用开发:探索现代移动开发的魅力
【8月更文挑战第12天】使用Swift进行iOS应用开发,不仅能够享受到Swift语言带来的简洁、快速、安全的编程体验,还能够充分利用iOS平台提供的丰富资源和强大功能。然而,iOS应用开发并非易事,需要开发者具备扎实的编程基础、丰富的实践经验和不断学习的精神。希望本文能够为您的iOS应用开发之旅提供一些有益的参考和帮助。
|
Swift iOS开发 Kotlin
苹果iOS新手开发之Swift中实现类似Kotlin的作用域函数
Swift可通过扩展实现类似Kotlin作用域函数效果。如自定义`let`, `run`, `with`, `apply`, `also`,增强代码可读性和简洁性。虽无直接内置支持,但利用Swift特性可达成相似功能。
249 7
|
调度 Swift Android开发
苹果iOS新手开发之Swift中的并发任务和消息机制
Swift的消息机制类似Android的Handler,实现任务调度有三种方式: 1. **Grand Central Dispatch (GCD)**:使用`DispatchQueue`在主线程或后台线程执行任务。 2. **OperationQueue**:提供高级接口管理`Operation`对象。 3. **RunLoop**:处理事件如输入源、计时器,类似Android的`Looper`和`Handler`。 **示例**: - GCD:在不同线程执行代码块。 - OperationQueue:创建操作并执行。 - RunLoop:用Timer添加到RunLoop中。
297 2
|
安全 编译器 Swift
探索iOS开发:Swift语言的现代魔法
【7月更文挑战第11天】本文深入探讨了Swift编程语言,它如何革新iOS开发领域,以及它为开发者带来的独特优势。我们将从Swift的基础语法出发,通过实际案例分析其性能优化技巧,最后讨论Swift在跨平台开发中的潜力。文章旨在为读者提供一个全面而深入的视角,了解Swift不仅仅是一门语言,更是一种推动创新的力量。
|
设计模式 前端开发 Swift
使用Swift进行iOS应用开发:深入探索与最佳实践
【5月更文挑战第24天】探索Swift在iOS开发中的深度应用与最佳实践。Swift以其简洁语法、类型安全、面向对象、高性能及与Objective-C的互操作性脱颖而出。使用Xcode设置开发环境,学习Swift语法,创建并设计项目,编写业务逻辑,同时进行调试和测试。遵循MVC模式,利用SwiftUI、并发特性,并注重内存管理,持续学习新工具和技术,以实现高质量应用开发。