也谈使用NSUserDefaults处理默认值

在Cocoa中,NSUserDefaults类的API用于存储和获取用户偏好设置。最近在了解设置webview的UserAgent时第一次用到了NSUserDefaults的registerDefaults:方法,顺带了解一下该方法。

始终使用objectForKey:方法

我一直建议大家使用objectForKey:方法来获取值,而不是直接使用诸如boolForKey:integerForKey:stringForKey:之类的方法。因为通过-[NSUserDefaults objectForKey:]方法来获取值并加上类型判断,可以增强我们程序的健壮性。看下面的例子:


BOOL showTutorial = [[NSUserDefaults standardUserDefaults] boolForKey:@"ShowTutorial"];

此时你没有办法判断到底存的值是NO还是没有存过值,如果想做类型区分就很难了,换成下面这样就好区分好理解了:


NSNumber *showTutorial = [[NSUserDefaults standardUserDefaults] objectForKey:@"ShowTutorial"];
BOOL shouldShowTutorial = NO;
if(!showTutorial){
  //no set yet, put some Extra logic
  shouldShowTutorial = YES;
}else{
  shouldShowTutorial = [showTutorial boolValue];
}

关于registerDefaults:方法

registerDefaults:方法可以注册一个包含APP偏好设置默认值的字典,设置之后,在APP中通过objectForKey:方法获取值时,如果之前没有保存过值,则返回通过registerDefaults:方法注册的默认值。你必须记住的一点是:registerDefaults:方法注册的默认值不会存储到硬盘,只在程序生命周期中有效,所以,你必须每次在程序启动时调用该方法去配置,通常在application:didFinishLaunchingWithOptions:方法中配置。

例子胜千言:



NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

//重新启动,之前通过registerDefaults设置的值不存在,说明没有做永久存储。
NSDictionary *rep = [ud dictionaryRepresentation];
NSLog(@"rep=%@", rep);    


[ud removeObjectForKey:@"name"];
[ud setObject:@"Apes" forKey:@"name"];
[ud registerDefaults:@{@"Apes Talk": @"male"}];
NSLog(@"name=%@",[ud objectForKey:@"name"]);//Apes
[ud removeObjectForKey:@"name"];
NSLog(@"name=%@",[ud objectForKey:@"name"]);//ApesTalk




[ud removeObjectForKey:@"twiceKey"];
[ud registerDefaults:@{@"twiceKey": @"Apes"}];
[ud registerDefaults:@{@"twiceKey": @"ApesTalk"}];
NSLog(@"twiceKey=%@", [ud objectForKey:@"twiceKey"]);//ApesTalk
[ud synchronize];


rep = [ud dictionaryRepresentation];
NSLog(@"rep=%@", rep);

一个常见的使用registerDefaults:方法的例子是设置webview的userAgent,客户端通过在userAgent中加入特殊标识,HTML页面通过这个标识来判断该页面是在APP中加载的还是在浏览器中加载的,以此可以做一些定制化的功能。这里以UIWebView为例,WKWebView在iOS9之后有一个新的设置userAgent的属性customUserAgent。



//获取webview的默认userAgent
UIWebView *tmpWebView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString *oldAgent = [tmpWebView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
//这里以简单的在旧的userAgent后面新增特殊标志为例
NSString * newAgent = [oldAgent stringByAppendingString:@" isInApp"];
//注册
NSDictionary * dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
[[NSUserDefaults standardUserDefaults] synchronize];

这个配置是怎么生效的呢?为什么通过NSUserDefaults设置的默认值为什么会影响到webview?webview在加载的时候是怎么读取userAgent的值的呢?

实际上,NSUserDefaults有个域domain(由不同级别的层次组成)的概念,每当你获取key对应的value时,NSUserDefaults会在这个域下由上到下查找对应的key,返回第一个找到的值,有点像响应链。域可以持久化存储在硬盘中或只是存储在内存中。

最重要的一个域是应用程序域(application domain),这里存储着调用set...ForKey:保存的APP设置信息。相对的,通过调用registerDefaults:方法保存的信息存储在低优先级的内存中的registration domain中,作为在应用程序域中找不到的任何值的一个后备(fallback)。

还有一个全局的域,存储系统范围的设置。特定语言的域存储地区偏好,如每个地区的月名称或日期格式。

最后但并非不重要的是,苹果使用相同的技术使我们能够通过命令行参数覆盖用户默认值。每个Cocoa应用程序自动检查命令行参数的键/值对,并将这些添加到适当命名的参数域(argument domain)下。由于参数域(argument domain)的优先级最高,我们可以使用它来临时覆盖任何偏好值。

看到这里我们再回过头想想设置UserAgent的问题,实际上,我们通过高优先级的参数域(argument domain)来临时修改了APP的偏好设置。然后我们可以大胆的猜测一下,UIWebView在加载的时候内部会自动从APP的偏好设置中读取UserAgent的值,所以我们设置的UserAgent才会生效。

完整的NSUserDefaults的域搜索顺序如下:

Domain State
NSArgumentDomain volatile
Application volatile
NSGlobalDomain persistent
Languages persistent
NSRegistrationDomain volatile

另外,通过Settings.bunlde设置的偏好设置信息也需要在程序启动时通过registerDefaults:方法注册才有效的。Settings.bunlde是个有意思的话题,不过目前使用该技术的APP较少,感兴趣的朋友可以看下这篇文章:iOS开发中Settings.bundle的使用

参考:

Handling Default Values With NSUserDefaults

iOS开发中Settings.bundle的使用

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

推荐阅读更多精彩内容

  • NSUserDefaults是什么,有什么用处 对于应用来说,每个用户都有自己的独特偏好设置,而好的应用会让用户根...
    heigo阅读 167评论 0 0
  • 对于应用来说,每个用户都有自己的独特偏好设置,而好的应用会让用户根据喜好选择合适的使用方式,把这些偏好记录在应用包...
    肉肉要次肉阅读 687评论 0 0
  • 概述: NSUserDefaults,官网上的定义是一个用户默认数据库的接口,在应用程序的启动过程中,持久地存储键...
    浅_若清风阅读 7,162评论 1 9
  • 欢迎关注公众号: CodeReview 关于NSUserDefaults首先要看苹果官方的定义 NSUserDef...
    sellse阅读 79,265评论 16 169
  • 关于首选项和设置 首选项是您持久存储的信息,并用于配置您的应用程序。应用程序通常会向用户公开偏好设置,以便他们自定...
    nicedayCoco阅读 989评论 0 0