應用程序首選項(application preference)及數據存儲


應用程序首選項(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初始化的對象,需要顯式調用此方法;其它方法創建的對象會自動打開文件,該對象被銷燬時會自動關閉該方法,不需顯式調用此方法。

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

以下摘自網絡,待整理:

Part1
Part2
posted @ 2013-11-25 17:14 wayne23 閱讀(559) 評論(0) 編輯 收藏
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章