应用程序首选项(application preference)及数据存储

应用程序首选项(application preference)用来存储用户设置,考虑以下案例:

a. 假设有一款MP3播放器程序,当用户调节了音量,当下次运行该程序时,可能希望保持上一次调节的音量值。

b. 一款游戏的难易度设置。

c. Twitter等社交程序的用户名和密码设置。

iOS应用程序存储信息的方式很多,但主要有如下3种:

1. 单例类NSUserDefaults:NSUserDefaults类的工作原理类似于NSDirectionary,所有首选项都以键/值对的方式存储在NSUserDefaults单例中。

2. 设置束(settings bundle):提供了一个通过iOS应用程序Settings对应用程序进行配置的接口。

3. 直接访问文件系统:能够读取属于当前应用程序的iOS文件系统部分的文件。

单例类NSUserDefaults

先获取NSUserDefaults单例的引用:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

然后便可以读写默认设置数据库了,写入值必须使用6个函数之一:setBool:forKey、setFloat:forKey、setInteger:forKey、setObject:forKey、setDouble:forKey、setURL:forKey。函数setObject:forKey可用于存储NSString、NSDate、NSArray以及其他常见的对象类型。例如:

[userDefaults setInteger:18 forKey:@"age"];
[userDefaults setObject:@"Tom" forKey:@"name"];

将数据写入NSUserDefaults时,并不一定会立即保存这些数据。为确保所有数据都写入了NSUserDefaults,可使用方法synchronize:

[userDefaults synchronize];

可以根据键读取值:

NSString *name = [userDefaults stringForKey:@"name"];
NSInteger *age = [userDefaults integerForKey:@"age"];

不同于写入值只提供了6个特有的函数,读取值提供了许多用于特定类型的方法,可以轻松地将存储的对象赋给特定类型的变量。根据要读取的数据类型,选择arrayForKey、boolForKey、dataforKey、dictionaryForKey、floatForKey、integerForKey、objectForKey、stringArrayForKey、doubleForKey或URLForKey。

操作NSUserDefaults实际上是对一个plist文件进行了编辑,在设备上运行程序时,plist将存储在设备中;但如果在模拟器中运行程序,模拟器将使用计算机硬盘来存储plist文件,存储路径为:/Users/<your username>/Library/Application Support/iPhone Simulator/<Device OS Version>/Applications/<your project folder>/Library/Preferences/com.yourcompany.projectname.plist。

P.s.1 在Xcode中测试时,如果需要在模拟器中利用iOS任务管理器结束正在开发的应用程序,需要先把Xcode调试停止掉(Stop),否则Xcode会抛异常。

P.s.2 在Lion以上的OS要访问/Users/<your username>/Library,可以在"前往"菜单按住"Option键",就会多出来一个"资源库(Library)"的选项。

设置束(settings bundle)

设置束也是对一个plist文件进行编辑,它的优点在于,可以通过Xcode plist编辑器来操作,无需额外编写代码,只需要在编辑器里定义要存储的数据及其键即可。

默认情况下应用程序没有设置束,可选择菜单File->New File->Resource->Settings Bundle来添加。这里有两个细节需要注意:(1) 最好是将文件建立在Supporting Files文件夹下,这样比较规范。(2)虽然新建的时候可以修改Bundle的名称,但强烈建议使用默认名称"Settings",因为在我多次测试过程中,发现使用了非默认名称后,设置束无法生效(或是无法显示)的情况。

新建成功后,可以看到一个Settings.bundle的目录,它包含一个en.lproj文件夹和一个Root.plist文件;其中en.lproj文件夹又包含有一个Root.strings的文件,这个主要是用于多语言化的。最主要的是这个Root.plist,它包含了一个设置列表,我们主要针对这个文件进行修改与自定义。

P.s 新建一个plist的方法比较特殊,无法直接在Xcode里完成,一个替代的方法是:先打开Settings.bundle所在目录,然后右键"显示包内容",然后在Finder里进行复制粘贴。

设置束中的文件Root.plist决定了应用程序首选项如何出现在应用程序Settings中。有7种类型的首选项,分别为:

Group -- 编组。键为PSGroupSpecifier,首选项逻辑编组的标题。
Text Field -- 文本框。键为PSTextFieldSpecifier,可编辑的文本字符串。
Title -- 标题。键为PSTitleValueSpecifier,只读文本字符串。
Toggle Switch -- 开关。键为PSToggleSwitchSpecifier,开关按钮。
Slide -- 滑块。键为PSSliderSpecifier,取值位于特定范围内的滑块。
Multivalue -- 多值。键为PSMultiValueSpecifier,下拉式列表。
Child Pane -- 子窗格。键为PSChildPaneSpecifier,子首选项页。

一些类型的特定属性说明:

Text Field

Text Field is Secure -- 是否为安全文本。如果设置为YES,则内容以圆点符号出现。

Autocapitalization Style -- 自动大写。有四个值: None(无)、Sentences(句子首字母大写)、Words(单词首字母大写)、All Characters(所有字母大写)。

Autocorrection Style -- 自动纠正拼写,如果开启,你输入一个不存在的单词,系统会划红线提示。有三个值:Default(默认)、No Autocorrection(不自动纠正)、Autocorrection(自动纠正)。

Keyboard Type -- 键盘样式。有五个值:Alphabet(字母表,默认)、Numbers and Punctuation(数字和标点符号)、Number Pad(数字面板)、URL(比Alphabet多出了.com等域名后缀)、Email Address(比Alphabet多出了@符合)。

Toggle Switch

Value for ON -- 当开关置为ON时,取得的字符串值。

Value for OFF -- 当开关置为OFF时,取得的字符串值。

Slider

Minimum Value -- 最小值,Number类型。

Maximum Value -- 最大值,Number类型。

Min Value Image Filename -- 最小值那一端的图片。

Max Value Image Filename -- 最大值那一端的图片。

P.s.图片大小必须为21*21,并且要放在Settings.bundle包内(在Finder里显示包内容,然后粘贴)。

Multivalue

Values -- 值的集合。

Titles -- 标题的集合,与值一一对应。

Child Pane

Filename -- 子plist的文件名。

设置束只是以一种可视化的方式来操作NSUserDefaults,所以取值的方式也完全相同,需要特别注意的是:当用户运行程序前,假如根本就没有通过设置束修改过任何值,这样即使设置了项的默认值,也会返回null,解决方法是在初始化代码里调用registerDefaults进行注册。

- (void)viewDidLoad
{
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"无名氏",@"nickname",@"beijing",@"city",nil];
    [userDefaults registerDefaults:dict];
    [userDefaults synchronize];
    
    NSString *nickName = [userDefaults stringForKey:@"nickname"];
    NSString *city = [userDefaults stringForKey:@"city"];
    self.lblNickName.text = [NSString stringWithFormat:@"nickname is %@", nickName];
    self.lblCity.text = [NSString stringWithFormat:@"city is %@", city];

    [super viewDidLoad];
}

还有另外一种更智能的方法,就是在AppDelegate.m的didFinishLaunchingWithOptions方法里读取项的DefaultValue,然后根据这个来赋值;但是如果没有设置DefaultValue,则会产生一个插入nil值的异常。

- (void)registerDefaultsFromSettingsBundle 
{  
    NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];  
    if(!settingsBundle) {  
        NSLog(@"Could not find Settings.bundle");  
        return;  
    }  
    
    NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:@"Root.plist"]];  
    NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];  
    
    NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];  
    for(NSDictionary *prefSpecification in preferences) {  
        NSString *key = [prefSpecification objectForKey:@"Key"];  
        if(key) {  
            [defaultsToRegister setObject:[prefSpecification objectForKey:@"DefaultValue"] forKey:key];  
        }  
    }  
    [[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [self registerDefaultsFromSettingsBundle];
    return YES;
}

直接访问文件系统

存储基本步骤是:获取Documents路径-->在路径下创建一个可读写文件-->利用NSFileHandle编辑文件内容

- (void)storeData
{
    NSString *strSave = @"Here is my private data";
    
    NSString *strDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    
    NSString *strFile = [strDir stringByAppendingPathComponent:@"mydata.csv"];
    
    if(![[NSFileManager defaultManager] fileExistsAtPath:strFile])
    {
        [[NSFileManager defaultManager] createFileAtPath:strFile contents:nil attributes:nil];
    }
    
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:strFile];
    [fileHandle seekToEndOfFile];
    //[fileHandle truncateFileAtOffset:0]; //清空内容
    [fileHandle writeData:[strSave dataUsingEncoding:NSUTF8StringEncoding]];
    [fileHandle closeFile];
}

读取基本步骤是:获取Documents路径-->访问可读写文件-->利用NSFileHandle读取文件内容获取Documents路径

- (void)readData
{
    NSString *strDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    
    NSString *strFile = [strDir stringByAppendingPathComponent:@"mydata.csv"];
    
    if([[NSFileManager defaultManager] fileExistsAtPath:strFile])
    {
        NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:strFile];
        NSString *strData = [[NSString alloc] initWithData:[fileHandle availableData] encoding:NSUTF8StringEncoding];
        [fileHandle closeFile];
        NSLog(@"%@",strData);
    }
}

一些概念性的东西:

iPhone会为每一个应用程序生成一个私有目录,这个目录位于/Users/*your user name*/Library/Application Support/iPhone Simulator/5.0/Applications,并随机生成一个数字+字母的目录名,在每一次应用程序启动时,这个目录名都会随机变化。

在程序的目录下有三个常用的文件夹:Documents、Caches和tmp。

  • Documents:应用中用户数据可以放在这里,iTunes备份和恢复的时候会包括此目录
  • tmp:存放临时文件,iTunes不会备份和恢复此目录,此目录下文件可能会在应用退出后删除
  • Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除

Foundation框架中的NSSearchPathForDirectoriesInDomains函数用于取得几个应用程序相关目录的全路径。在iOS上使用这个函数时,第一个参数NSSearchPathDirectory用于指定搜索路径常量,第二个参数NSSearchPathDomainMask使用NSUserDomainMask常量,第三个参数BOOL expandTilde表示是否展开成完整路径。

常用的搜索路径常量:

NSDocumentDirectory -- <Application_Home>/Documents

NSCachesDirectory -- <Application_Home>/Library/Caches

NSApplicationSupportDirectory -- <Application_Home>/Library/Application Support

所有的NSUserDomainMask常量:

NSUserDomainMask = 1, //用户主目录中

NSLocalDomainMask = 2, //当前机器中

NSNetworkDomainMask = 4, //网络中可见的主机

NSSystemDomainMask = 8, //系统目录,不可修改(/System)

NSAllDomainsMask = 0x0ffff //全部

P.S. 可以在Xcode点击NSSearchPathDirectoryNSSearchPathDomainMask链接到定义类,查看所有的枚举值及部分注释。

通常使用Documents目录进行持久化数据的保存,而这个Documents目录可以通过NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserdomainMask,YES) 得到,这个方法返回的是一个数组,因为NSSearchPathForDirectoriesInDomains函数最初是为Mac OS X设计的,而Mac OS X上可能存在多个这样的目录,所以它的返回值是一个路径数组,而不是单一的路径。在iOS上,结果数组应该只包含一个给定目录的路径,所以可以通过取索引0来直接获取:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSLog(@"%@",documentsDirectory);
//输出:/Users/wayne/Library/Application Support/iPhone Simulator/5.0/Applications/96CFD458-361A-4D7E-82A7-DA6699DC0F96/Documents

也可以使用NSHomeDirectory函数返回顶级Home目录的路径--也就是包含应用程序、Documents、Library和tmp目录的路径:

NSString *homePath = NSHomeDirectory();
NSLog(@"%@",homePath);
//输出:/Users/wayne/Library/Application Support/iPhone Simulator/5.0/Applications/96CFD458-361A-4D7E-82A7-DA6699DC0F96

文件管理类NSFileManager常用操作:

创建一个文件管理器

NSFileManager *fm = [NSFileManager defaultManager];

浅度遍历目录

- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error

深度遍历目录

- (NSArray *)subpathsOfDirectoryAtPath:(NSString *)path error:(NSError **)error

获取当前目录

- (NSString *)currentDirectoryPath

更改当前目录

- (BOOL)changeCurrentDirectoryPath:(NSString *)path

枚举目录内容

- (NSDirectoryEnumerator *)enumeratorAtPath:(NSString *)path

创建目录

- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error

创建文件

- (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)contents attributes:(NSDictionary *)attributes

复制文件

- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error

删除文件

- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error

目录/文件拷贝

- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error

移动/重命名文件或者目录

- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error

测试文件是否存在

- (BOOL)fileExistsAtPath:(NSString *)path

获取文件信息(属性和权限)

- (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error

从文件中读取数据

- (NSData *)contentsAtPath:(NSString *)path

比较两个文件的内容

- (BOOL)contentsEqualAtPath:(NSString *)path1 andPath:(NSString *)path2

测试文件是否存在,且是否能执行读操作

- (BOOL)isReadableFileAtPath:(NSString *)path

测试文件是否存在,且是否能执行写操作

- (BOOL)isWritableFileAtPath:(NSString *)path

文件操作类NSFileHandle常用操作:

只读方式打开文件

+ (id)fileHandleForReadingAtPath:(NSString *)path

只写方式打开文件

+ (id)fileHandleForWritingAtPath:(NSString *)path

读写方式打开文件

+ (id)fileHandleForUpdatingAtPath:(NSString *)path

从文件当前位置读到结尾

- (NSData *)readDataToEndOfFile

从文件当前位置读固定字节数的内容

- (NSData *)readDataOfLength:(NSUInteger)length

返回所有可用的数据

- (NSData *)availableData

写文件

- (void)writeData:(NSData *)data

定位到文件尾部

- (unsigned long long)seekToEndOfFile

定位到文件指定位置

- (void)seekToFileOffset:(unsigned long long)offset

获取当前文件的偏移量

- (unsigned long long)offsetInFile

将文件的长度设置为offset字节

- (void)truncateFileAtOffset:(unsigned long long)offset

关闭文件

- (void)closeFile

P.S. (网络socket中)通过initWithFileDescriptor初始化的对象,需要显式调用此方法;其它方法创建的对象会自动打开文件,该对象被销毁时会自动关闭该方法,不需显式调用此方法。

=======================================================================

以下摘自网络,待整理:

下面举个例子介绍下NSFileHandle的具体用法
    NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:PATH];//以只读方式打开文件生成文件句柄
    NSData *data = [fh readDataOfLength:3]; //从文件读取指定字节的内容
    data = [fh readDataOfLength:5];//从上次读取的位置往后再读5个字节
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
如果文件内容不长的话,可以一次性读到结尾。
  
    NSData *data = [fh readDataToEndOfFile];//一次性的把文件中的内容全读出来

以上介绍的是如何读取文件下面来介绍如何写入文件

 NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:PATH];//以只写方式打开文件生成文件句柄

[fh writeData:[@"hello" dataUsingEncoding:NSUTF8StringEncoding]];//这将替换PATH目录下文件的前五个字节剩下的内容不变
 如果我们想把文件清空再重新写入该怎么办呢?

[fh truncateFileAtOffset:0];//将文件内容截断至0字节 这样就会把内容清空


那么我们能不能保持原文件内容不变往后面追加内容呢,也是可以的
[fh seekToEndOfFile]; //我们先把读写指针设在文件的尾端
[fh writeData:[@"hello" dataUsingEncoding:NSUTF8StringEncoding]];
writeToFile 也用于文件的写入它和NSFileHandle有什么区别呢

[@"" writeToFile:PATH atomically:YES encoding:NSUTF8StringEncoding error:nil];
writeToFile往文件里面写数据,都是覆盖式写入的 atomically的YES 或 NO YES 表示保证文件的写入原子性,就是说会先创建一个临时文件,直到文件内容写入成功再导入到目标文件里。 NO 则直接写入目标文件里。
如果要采用追回式的文件写入,也就是不覆盖原文件的内容可以采用NSFileHandle
Part1
常用路径工具函数
NSString * NSUserName(); 返回当前用户的登录名 
NSString * NSFullUserName(); 返回当前用户的完整用户名 
NSString * NSHomeDirectory(); 返回当前用户主目录的路径 
NSString * NSHomeDirectoryForUser(); 返回用户user的主目录 
NSString * NSTemporaryDirectory(); 返回可用于创建临时文件的路径目录 
 
常用路径工具方法
-(NSString *) pathWithComponents:components                         根据components中元素构造有效路径 
-(NSArray *)pathComponents                                          析构路径,获取路径的各个部分 
-(NSString *)lastPathComponent                                       提取路径的最后一个组成部分 
-(NSString *)pathExtension                                           路径扩展名 
-(NSString *)stringByAppendingPathComponent:path                    将path添加到现有路径末尾 
-(NSString *)stringByAppendingPathExtension:ext                     将拓展名添加的路径最后一个组成部分 
-(NSString *)stringByDeletingPathComponent                           删除路径的最后一个部分 
-(NSString *)stringByDeletingPathExtension                           删除路径的最后一个部分 的扩展名 
-(NSString *)stringByExpandingTildeInPath                            将路径中的代字符扩展成用户主目录(~)或指定用户主目录(~user) 
-(NSString *)stringByResolvingSymlinksInPath                         尝试解析路径中的符号链接 
-(NSString *)stringByStandardizingPath                               通过尝试解析~、..、.、和符号链接来标准化路径 
- 
使用路径NSPathUtilities.h 
tempdir = NSTemporaryDirectory(); 临时文件的目录名 
path = [fm currentDirectoryPath];
[path lastPathComponent]; 从路径中提取最后一个文件名 
fullpath = [path stringByAppendingPathComponent:fname];将文件名附加到路劲的末尾 
extenson = [fullpath pathExtension]; 路径名的文件扩展名 
homedir = NSHomeDirectory();用户的主目录 
component = [homedir pathComponents];  路径的每个部分 
 
NSProcessInfo类:允许你设置或检索正在运行的应用程序的各种类型信息
(NSProcessInfo *)processInfo                                  返回当前进程的信息
-(NSArray*)arguments                                           以NSString对象数字的形式返回当前进程的参数
-(NSDictionary *)environment                                   返回变量/值对词典。描述当前的环境变量
-(int)processIdentity                                          返回进程标识
-(NSString *)processName                                       返回进程名称
-(NSString *)globallyUniqueString                              每次调用该方法都会返回不同的单值字符串,可以用这个字符串生成单值临时文件名   
-(NSString *)hostname                                          返回主机系统的名称 
-(unsigned int)operatingSystem                                 返回表示操作系统的数字 
-(NSString *)operatingSystemName                                     返回操作系统名称 
-(NSString *)operatingSystemVersionString                                     返回操作系统当前版本
-(void)setProcessName:(NSString *)name                                将当前进程名称设置为name 
过滤数组中的文件类型  : [fileList pathsMatchingExtensions:[NSArrayarrayWithObject:@"jpg"]];
///////////////////////////////////////////////////////////////////////////////////////////////////////////
基本文件操作NSFileHandle
常用NSFileHandle方法
(NSFileHandle *)fileHandleForReadingAtPath:path                        打开一个文件准备读取
(NSFileHandle *)fileHandleForWritingAtPath:path                        打开一个文件准备写入
(NSFileHandle *)fileHandleForUpdatingAtPath:path                        打开一个文件准备更新(读取和写入)
-(NSData *)availableData                                                  从设备或通道返回可用数据
-(NSData *)readDataToEndOfFile                                            读取其余的数据直到文件末尾(最多UINT_MAX字节)
-(NSData *)readDataOfLength:(unsigned int)bytes 从文件读取指定数目bytes的内容
-(void)writeData:data                  将data写入文件
-(unsigned long long) offsetInFile      获取当前文件的偏移量
-(void)seekToFileOffset:offset         设置当前文件的偏移量 
-(unsigned long long) seekToEndOfFile      将当前文件的偏移量定位的文件末尾
-(void)truncateFileAtOffset:offset        将文件的长度设置为offset字节
-(void)closeFile                           关闭文件
 
获取文件大小 
 
Using the C FILE type:
int getFileSizeFromPath(char * path)
{
     FILE * file;
     int fileSizeBytes = 0;
     file = fopen(path,"r");
     if(file>0){
         fseek(file, 0, SEEK_END);
         fileSizeBytes = ftell(file);
         fseek(file, 0, SEEK_SET);
         fclose(file);
     }
     return fileSizeBytes;
}
or in XCode use the NSFileManager:
NSFileManager * filemanager = [[NSFileManager alloc]init];
if([filemanager fileExistsAtPath:[self getCompletePath] isDirectory:&isDirectory]){
     NSDictionary * attributes = [filemanager attributesOfItemAtPath:[self getCompletePath] error:nil];
// file size
     NSNumber *theFileSize;
     if (theFileSize = [attributes objectForKey:NSFileSize])
       _fileSize= [theFileSize intValue];
}
Part2
posted @ 2013-11-25 17:14  CoderWayne  阅读(4289)  评论(0编辑  收藏  举报