第十三章: User Defaut

第十三章: User Defaut

大部分的程序都会有Preferences Panel来让用户设置偏好的外观和功能.用户选择的偏好设置会保存user default数据库中,在用户Home目录中: ~/Library/Preferences.找到数据库文件,一般为property list格式 我们可以使用程序 Property List Editor来浏览这些文件

我们通过NSUserDefaults类来注册程序的出厂设置,保存用户偏好设置,以及读取之前保存过得用户偏好设置

在12章中我们创建了一个color well控件,我们将用它来设定table view的背景颜色.当用户修改了他的preference,程序将会把新的preference写入数据库. 当创建新的document窗口时. 程序会从user default数据库中读取preference. 只有当重新创建一个window,改变才生效,如图13.1


你有注意到每次运行程序时,一个untitled document同时出现么? 这个check box:Automatically Open new document将用来控制程序启动时是否要同时打开一个untitled document

--- NSDictionary 和 NSMutableDictionary ---

在关注user defaults之前,我们需要对类NSDictonary (图13.2)和NSMutableDictionary有所了解. 一个dictionary就是key-vaule对的集合. key是字符串, vuale是对象指针

dictionary中描述key的字符串应该是唯一的.我们可以通过方法 objectForKey:来取得对应于某个key的value
anObject = [myDictionary objectForKey:@"foo"];
如果dictionary中没有对应的key. 方法返回nil

NSMutableDictionary是NSDictionary的子类. NSDictionary在创建的时候其中所有的key和对象的value都存在了, 你可以访问其中的内容,而不能修改. 而NSMutableDictionary可以让你来添加和删除key和vaule

->NSDictionary
dictionary使用hash表来实现,所以查找的速度很快. 以下是类NSDictionary的一些常用方法
- (NSArray *)allKeys
返回一个包含所有key的array

- (unsigned)count
返回有多少对key-value

- (id)objectForKey:(NSString *)aKey
返回和aKey相关联的value,如果没有和aKey相关联的value,则返回nil

- (NSEnumerator *)keyEnumerator
Enumerators也就是iterators或enumerations.我们可以使用它来一步一步迭代出集合中的所有成员. 这个方法是从一个dictionary中得到一个key的迭代器. 下面是我们可能使用它来列举所有的key-vaule对
NSEnumerator *e = [myDict keyEnumerator];
for (NSString *s in e) {
   NSLog(@"key is %@, value is %@", s, [myDict objectForKey:s]);
}

- (NSEnumerator *)objectEnumerator
和前一个一样,这个得到value的迭代器.(NSArray也有一个类似的方法得到array的成员迭代器 : objectEnumerator)

-> NSMutableDictionary
+ (id)dictionary
创建一个空的dictionary

- (void)removeObjectForKey:(NSString *)aKey
从dicionary中删除一条记录,aKey以及和它对应的value

- (void)setObject:(id)anObject forKey:(NSString *)aKey
使用aKey和anObject组成一条记录添加到dictionary中.  在添加之间, 将会发送retain消息给anObject. 如果aKey已经存在于dictionary中,那么会移除原来对应的value object,使用新的anObject代替.同时发送release 消息给原来的vaule object


--- NSUserDefaults ---

每个程序有会有一些出厂默认设置. 当用户修改他的defaults时, 只有和出厂默认设置不同的user defaults会存储在user default数据库. 所以当程序启动时,我们需要首先使用出厂默认设置. 这个过程叫: registering defaults

当registering defaults完成后, 我们将使用user defaults配置用户所需. 这个过程叫做: reading and using the defaults. user defaults database中的数据将会自动从文件系统中读取.

你也有可能创建一个Preferences panel来让用户设置defaults, 对于defaults 对象的改变会自动写入文件系统中.这个过程叫: setting the defaults 图13.3


以下是NSUserDefaults类的一些常用方法
+ (NSUserDefaults *)standardUserDefaults
返回全局标准 defaults 对象

- (void)registerDefaults:(NSDictionary *)dictionary
注册出厂默认设置

- (void)setBool:(BOOL)value   forKey:(NSString *)defaultName
- (void)setFloat:(float)value forKey:(NSString *)defaultName
- (void)setInteger:(int)value forKey:(NSString *)defaultName
- (void)setObject:(id)value   forKey:(NSString *)defaultName
修改defaults方法

- (BOOL)boolForKey:(NSString *)defaultName
- (float)floatForKey:(NSString *)defaultName
- (int)integerForKey:(NSString *)defaultName
- (id)objectForKey:(NSString *)defaultName
对其defaults方法.如果用户没有修改过,返回出厂默认设置

- (void)removeObjectForKey:(NSString *)defaultName
删除用户设置,程序将使用出厂默认设置

-->不同类型的defaults的优先级
到目前,我们讨论了两种类型的defaults. 用户所希望的user default和出厂默认设置default. 其中前者优先级高于后者.[这里的优先级理解是这样的:假如存在多种defauts, 我们使用NSUserDefaults类去读写default是,会先去看那个类型的default].实际上,还有几种优先级存在. 这些优先级level我们称之为domains. 下面列举了程序可以用地的节domain. 优先级从高到低
. arguments: 通过command line传递. 一般我们都是通过双击程序图标来运行程序,而不是使用command line. 所以我们很少会用到
.Application: 来自于user defaults database
.Global: 用户对于整个提供的设定
.Language: 基于用户所选语言
.Registered defaults: 程序的出厂默认设置

--- 设置程序的Identifier ---
在~/Library/Preferences中为程序创建的property list文件叫什么名字呢?默认的,以程序的identifier.我们在第10章设置了程序的identifier为 com.bignerdranch.RaiseMan. 所以文件名将为com.bignerdranch.RaiseMan.plist


--- 给Defaults Key命名 ---

我们可能在不同的几个类中register,read,write defaults. 为了保证我们能使用一样的key名字,我们将名字字符串定义在一个文件中,然后在使用的地方使用#import来包含它

我们可以使用C的预编译#define, 不过Cocoa程序员一般选择使用全局变量来实现. 在PreferenceController.h #import只后添加
extern NSString * const BNRTableBgColorKey;
extern NSString * const BNREmptyDocKey;

在PreferenceController.m中定义这些变量,(在#import后, @implementation前)
NSString * const BNRTableBgColorKey = @"TableBackgroundColor";
NSString * const BNREmptyDocKey = @"EmptyDocumentFlag";

那我们为什么要声明全局变量来包含一个常量字符串? 我们可以直接使用这个字符串就好了阿. 这里有个问题,当你直接使用字符串时,假如发生拼写错误的时候,编译器不会报错,而是使用错误的字符串. 相反,如果我们使用全局变量,如果拼写错了,编译器会报错的.

为了使全局变量能和其他公司组织的全局变量区别开来,我们使用前缀 BNR (Big Nerd Ranch). Cocoa的全局变量前缀是NS. 当我们需要使用第三方的组件来开发时,这样的前缀规则是十分重要的[名字空间概念] (类名也是全局的. 我们自己的类应该有BNR的前缀,这样来区别于其他组织的类)


--- Registering Defaults ---

在接受其他消息前,每个类首先都会接受的initialize 消息. 我们重载 Appcontroller的initialize方法,来区别先register defaults
+ (void)initialize
{
    // Create a dictionary
    NSMutableDictionary *defaultValues = [NSMutableDictionary dictionary];

    // Archive the color object
    NSData *colorAsData = [NSKeyedArchiver archivedDataWithRootObject:
                                               [NSColor yellowColor]];

    // Put defaults in the dictionary
    [defaultValues setObject:colorAsData forKey:BNRTableBgColorKey];
    [defaultValues setObject:[NSNumber numberWithBool:YES]
                      forKey:BNREmptyDocKey];

    // Register the dictionary of defaults
    [[NSUserDefaults standardUserDefaults]
                                        registerDefaults: defaultValues];
    NSLog(@"registered defaults: %@", defaultValues);
}

这个方法是class method,所以有+ 号的前缀

注意到我们将color对象先储存为一个data对象. 这是因为,NSColor对象不知道怎么样将自己写入到property list, 所以我们先将它们打包成一个data对象. property list 类 - NSString,NSArray,NSDictionary,NSCalendarDate,NSData和NSNumber- 知道怎么将自己写入property list. 一个property list任何是这些类对象的组合.例如,一个dictionary可以包含一个date array.[日期对象的array, 如果array 中存放的是NSColor对象,会怎么样呢.我估计是不能写入到property list中,大家可以试试看]


--- 让用户编辑defaults ---

下来,我们要修改PreferenceController类使用defaults database更新Preference Panel. 在PreferenceController.h中声明以下的方法
- (NSColor *)tableBgColor;
- (BOOL)emptyDoc;

修改PreferenceController.m 如下
#import "PreferenceController.h"

NSString * const BNRTableBgColorKey = @"TableBackgroundColor";
NSString * const BNREmptyDocKey = @"EmptyDocumentFlag";

@implementation PreferenceController

- (id)init
{
    if (![super initWithWindowNibName:@"Preferences"])
        return nil;
    return self;
}

- (NSColor *)tableBgColor
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *colorAsData = [defaults objectForKey:BNRTableBgColorKey];
    return [NSKeyedUnarchiver unarchiveObjectWithData:colorAsData];
}

- (BOOL)emptyDoc
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    return [defaults boolForKey:BNREmptyDocKey];
}

- (void)windowDidLoad
{
    [colorWell setColor:[self tableBgColor]];
    [checkbox setState:[self emptyDoc]];
}

- (IBAction)changeBackgroundColor:(id)sender
{
    NSColor *color = [colorWell color];
    NSData *colorAsData =
                   [NSKeyedArchiver archivedDataWithRootObject:color];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:colorAsData forKey:BNRTableBgColorKey];
}

- (IBAction)changeNewEmptyDoc:(id)sender
{
    int state = [checkbox state];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setBool:state forKey:BNREmptyDocKey];
}

@end

在windowDidLoad方法中, 我们读取了defualts中的数据,并用它让color well和check box更新当前设定. 在changeBackgroundColor:和changeNewEmptyDoc:方法中我们更新了defaults database

现在我们编译运行程序, 程序将读写defaults database. 不过,我们还没有使用这些信息来做什么, 所以文档的背景颜色还是白色.而untitled document还是会在打开程序时弹出来


--- 使用Defaults ---

现在我们打算使用这些defaults. 首先,我们将AppController设置成NSApplication对象的delegate,根据user defaults, 禁止生成一个untitled document. 然后,设置table view中的背景颜色.

-> 禁止生成Untitled Documents
之前,我们通过2步来生成delegate: 实现delegate方法 和 将delegate outlet指向一个对象 如图13.4


在自动生成一个新的untitled document前,NSApplication对象会发送消息applicationShouldOpenUntitledFile:给它的deleate.在AppController.m中添加如下方法
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
    NSLog(@"applicationShouldOpenUntitledFile:");
    return [[NSUserDefaults standardUserDefaults]
                                   boolForKey:BNREmptyDocKey];
}

现在来设置delegate.打开MainMenu.nib文件, Control-click File's Owner-在这里,它是NSApplication对象-弹出连接窗口. 从delegate拖动到我们的AppController如图13.5

->设置Table View的背景颜色
当对应于一个新的document的nib文件成功unarchived时. 将会给我们的Mydocument对象发送消息winowControllerDidLoadNib:. 在这时候,我们就可以来更新table view的background color了.[其实我觉得,我们不需要去记住这些cocoa类的delegate方法,只有了解cocoa 类的基本架构,然后查阅.h或文档就可以了]

修改MyDocument.m中的这个方法如下
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
    [super windowControllerDidLoadNib:aController];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *colorAsData;
    colorAsData = [defaults objectForKey:BNRTableBgColorKey];

    [tableView setBackgroundColor:
           [NSKeyedUnarchiver unarchiveObjectWithData:colorAsData]];
}

同时,为了能使用这些全局变量,确保包含了PreferenceController.h. 编译运行程序.

--- 思考: NSUserDefaultsController ---
有时,我们希望能够将NSUserDefaults对象的值绑定, NSUserDefaultsController类可以做这样的事情.程序中的所有nib共同使用的一个共享的NSUserDefaultsController对象.

例如,对于Preference panel的check box,我们想要使用绑定来代替target/action.我们可以将他绑定到NSUserDefaultsController的value.EmptyDocumentFlag 如图13.6

--- 思考:使用Command line来读写Defaults ---
user defaults存放在~/Library/Preferences中.我们可以使用defaults命令通过命令行来编辑它们.例如,我们可以查看XCode的defaults, 打开Terminal输入以下命令
defaults read com.apple.Xcode
我们就可以看到XCode的defaults了.下面列出来了看到的开始几行:
{
    DocViewerHasSetPrefs = YES;
    NSNavBrowserPreferedColumnContentWidth = 155;
    NSNavLastCurrentDirectoryForOpen = "~/RaiseMan";
    NSNavLastRootDirectoryForOpen = "~";
    NSNavPanelExpandedSizeForOpenMode = "{518, 400}";
    NSNavPanelFileListModeForOpenMode = 1

同样,我们也可以修改defaults.输入下面的命令来修改NSOpenPanel打开的XCode默认的目录
defaults write com.apple.Xcode NSNavLastRootDirectoryForOpen /Users

再试试这个
defaults read com.bignerdranch.RaiseMan

要看我们的全局defaults
defaults read NSGlobalDomain

--- 挑战 ---
给Preference panel添加一个button,可以清除所有的user defaults. 命名为Reset Preferences. 不要忘记了使用默认的新defaults值来更新Preference窗口

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值