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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM