应用程序首选项(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点击NSSearchPathDirectory和NSSearchPathDomainMask链接到定义类,查看所有的枚举值及部分注释。
通常使用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
常用路径工具函数 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]; }