Swift下UserDefaults的常见使用和注意事项

前言

UserDefaults适合存储轻量级的本地客户端数据,这是一种常见的数据持久化方式。(建议:如果是存储大批量的数据不要使用这个方法)

基本用法

Swift2 and above

Store
UserDefaults.standard.set(true, forKey: "Key") //Bool
UserDefaults.standard.set(1, forKey: "Key")  //Integer
UserDefaults.standard.set("TEST", forKey: "Key") //setObject
Retrieve
 UserDefaults.standard.bool(forKey: "Key")
 UserDefaults.standard.integer(forKey: "Key")
 UserDefaults.standard.string(forKey: "Key")
Remove
 UserDefaults.standard.removeObject(forKey: "Key")
Remove all Keys
if let appDomain = Bundle.main.bundleIdentifier {
UserDefaults.standard.removePersistentDomain(forName: appDomain)
 }

Swift2 and below

Store
NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: "yourkey")
NSUserDefaults.standardUserDefaults().synchronize()
Retrieve
  var returnValue: [NSString]? = NSUserDefaults.standardUserDefaults().objectForKey("yourkey") as? [NSString]

Remove
 NSUserDefaults.standardUserDefaults().removeObjectForKey("yourkey")

其他用法

NSUbiquitousKeyValueStore

这个方法可以基于iCloud做跨设备的UserDefaults数据存储,参考NSUbiquitousKeyValueStore

dictionaryRepresentation

这个方法可以获得当前App存储的所有UserDefaults数据

didChangeNotification

这个通知可以在UserDefault发生改变时发出。可以考虑当这个通知发生时全局进行同步数据。UserDefaults.didChangeNotification

关于Synchronize()

在iOS7或者7以下,一般只会在app返回background的时候才会保存数据到disk,但是iOS8以及以上之后app都会在极其短的周期内去保存数据,除非极其频繁且大规模地进行写入的操作,一般而言都会在可接受的时间内完成这项操作。

在iOS8以及以上,读数据大约需要0.5微妙的时间,但是写入数据需要10倍左右的时间,需要将key-valu通过NSPropertyListSerialization转化成plist data

总而言之,iOS8以及以上的系统内不太建议使用synchronize()方法

比较好的实践-使用UserDefaults的方式

  1. 创建UserDefaults的拓展
  2. 创建枚举值存储需要的key
  3. 保存和提取值
Create extension of UserDefaults
Create enum with required Keys to store in local
Store and retrieve the local data wherever you want

示例

extension UserDefaults{

    //MARK: Check Login
    func setLoggedIn(value: Bool) {
        set(value, forKey: UserDefaultsKeys.isLoggedIn.rawValue)
        //synchronize()
    }

    func isLoggedIn()-> Bool {
        return bool(forKey: UserDefaultsKeys.isLoggedIn.rawValue)
    }

    //MARK: Save User Data
    func setUserID(value: Int){
        set(value, forKey: UserDefaultsKeys.userID.rawValue)
        //synchronize()
    }

    //MARK: Retrieve User Data
    func getUserID() -> Int{
        return integer(forKey: UserDefaultsKeys.userID.rawValue)
    }
}

enum for Keys used to store data

enum UserDefaultsKeys : String {
    case isLoggedIn
    case userID
}

Save in UserDefaults where you want

UserDefaults.standard.setLoggedIn(value: true)          // Bool
UserDefaults.standard.setUserID(value: result.User.id!) // String

Retrieve data anywhere in app

print("ID : \(UserDefaults.standard.getUserID())")
UserDefaults.standard.getUserID()

Remove Values

UserDefaults.standard.removeObject(forKey: UserDefaultsKeys.userID) 

注意事项

UserDefaults 的 integer(forKey:) 回傳 0 的問題

利用UserDefaults我们可以方便地存取一些简单的资料,然而当我们存取的资料类型是Int,Bool,Float,Double时,却会遇到一个特别的问题。因为她们回传的类型不是optional,所以不会返回nil,而是一个预设的值,比如0,false之类。可能我们存在一些需求,希望没有存储值时返回nil,那么有两种方式可以解决这个问题。

这个方法的返回值是不可选的,会有默认值

open func integer(forKey defaultName: String) -> Int
-boolForKey: is equivalent to -objectForKey:, except that it converts the returned value to a BOOL. If the value is an NSNumber, NO will be returned if the value is 0, YES otherwise. If the value is an NSString, values of "YES" or "1" will return YES, and values of "NO", "0", or any other string will return NO. If the value is absent or can't be converted to a BOOL, NO will be returned.

更多参考:

func boolForKey(defaultName: String) -> Bool
func integerForKey(defaultName: String) -> Int
func floatForKey(defaultName: String) -> Float
func doubleForKey(defaultName: String) -> Double
func objectForKey(defaultName: String) -> AnyObject?
func URLForKey(defaultName: String) -> NSURL?
func dataForKey(defaultName: String) -> NSData?
func stringForKey(defaultName: String) -> String?
func stringArrayForKey(defaultName: String) -> [String]?
func arrayForKey(defaultName: String) -> [AnyObject]?
func dictionaryForKey(defaultName: String) -> [String : AnyObject]?

有些是返回可选类型的

方案一

使用register(defaults:)设定找不到key对应的value时回传的预设值,比如nil

let dic = ["isFirstOpenApp": nil]
UserDefaults.standard.register(defaults: dic)

register设定的内容是暂存的,并没有存档,所以每次App启动时都要再设定一次。(并且没有设为nil这种操作,设为nil意味着取消该项的设置,后面取值时依旧会采用默认值)

方案二(可取)

通过回传Any?的object(forKey:)搭配as?转型判断

这个方法返回值是可选的

func object(forKey defaultName: String) -> Any?

比如:

if let number = UserDefaults.standard.object(forKey: "number") as? Int {
   print(number)
} else {
   print("no value")
}

由于回传的类型是Any?,所以找不到key number对应的value时会回传nil

参考链接:UserDefaults预设值

value(forKey:) 和 object(forKey:)

value(forKey:)是KVC的语法,它并不是一个UserDefaults的直接方法。所以最好不要在UserDefaults

Never use value(forKey:) on UserDefaults or Dictionary or any other class unless you have a clearly understood need to use key-value coding to get the desired result.

When you don't have such a need, use the standard access methods provided by UserDefaults object(forKey:)

再补充一点:
The value(forKey:) is not a UserDefaults-only method. It is enabled by the NSKeyValueCoding, which, According to Apple's Documentation.
NSKeyValueCoding is an informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface.

It happens that UserDefaults is NSKeyValueCoding compliant, so people have started (not necessarily in the correct way) using it for accessing UserDefaults.

简而言之,UserDefaults也是遵循了NSKeyValueCoding协议的,所以使用value(forKey:)也是可以获取到数据,但是不建议这种用法。在UserDefaults里面最好使用object(forKey:),这是标准用法

参考链接:在UserDefaults中object(forKey:)和value(forKey:)的区别

参考

NSUserDefaults — A Swift Introduction
[NSUserDefaults synchronize] is Planned to be Deprecated

以上大多是一些需要注意的问题。关于Swift的常见用法,已经有很多博客在详述了,可以参考:Swift:UserDefaults协议(Swift视角下的泛字符串类型API)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279

推荐阅读更多精彩内容

  • 源码加翻译 #import <Foundation/NSArray.h> #import <Foundation/...
    CAICAI0阅读 1,119评论 0 50
  • KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过K...
    暮年古稀ZC阅读 2,008评论 2 9
  • 关于键值编码 键值编码(KVC)是一种由NSKeyValueCoding非正式协议提供的机制,对象采用该机制来提供...
    渐z阅读 861评论 0 0
  • 官方文档点这里:Key-Value Coding Programming Guide、NSKey​Value​Co...
    阿斯兰iOS阅读 1,282评论 0 1
  • 这听起来是一个很明显的问题,但是它真这么简单吗? 我听到过公司里的一些开发人员讨论这个问题。当时我的一位高级工程师...
    尧淳阅读 222评论 0 2