應用程序首選項(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]; }
