你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS本地數據存取,看這裡就夠了

iOS本地數據存取,看這裡就夠了

編輯:IOS開發基礎

未標題-1500.jpg

本文授權轉載,作者:hosea_zhou(簡書)

應用沙盒

1)每個iOS應用都有自己的應用沙盒(應用沙盒就是文件系統目錄),與其他文件系統隔離。應用必須待在自己的沙盒裡,其他應用不能訪問該沙盒

2)應用沙盒的文件系統目錄,如下圖所示(假設應用的名稱叫Layer)

1353118-9887ba61908edade.jpg

應用沙盒的文件系統目錄

3)應用沙盒結構分析

  • 應用程序包:(上圖中的Layer)包含了所有的資源文件和可執行文件

  • Documents:保存應用運行時生成的需要持久化的數據,iTunes同步設備時會備份該目錄。例如,游戲應用可將游戲存檔保存在該目錄

  • tmp:保存應用運行時所需的臨時數據,使用完畢後再將相應的文件從該目錄刪除。應用沒有運行時,系統也可能會清除該目錄下的文件。iTunes同步設備時不會備份該目錄

  • Library/Caches:保存應用運行時生成的需要持久化的數據,iTunes同步設備時不會備份該目錄。一般存儲體積大、不需要備份的非重要數據

  • Library/Preference:保存應用的所有偏好設置,iOS的Settings(設置)應用會在該目錄中查找應用的設置信息。iTunes同步設備時會備份該目錄

4)應用沙盒目錄的常見獲取方式

  • 沙盒根目錄:NSString *home = NSHomeDirectory();

  • Documents:(2種方式):

i)利用沙盒根目錄拼接”Documents”字符串

NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@"Documents"];
// 不建議采用,因為新版本的操作系統可能會修改目錄名

ii)利用NSSearchPathForDirectoriesInDomains函數

// NSUserDomainMask 代表從用戶文件夾下找
// YES 代表展開路徑中的波浪字符“~”
NSArray *array =  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// 在iOS中,只有一個目錄跟傳入的參數匹配,所以這個集合裡面只有一個元素
NSString *documents = [array objectAtIndex:0];
  • tmp:NSString *tmp = NSTemporaryDirectory();

  • Library/Caches:(跟Documents類似的2種方法)

i)利用沙盒根目錄拼接”Caches”字符串

ii)利用NSSearchPathForDirectoriesInDomains函數(將函數的第2個參數改為:NSCachesDirectory即可)

  • Library/Preference:通過NSUserDefaults類存取該目錄下的設置信息

iOS應用數據存儲的常用方式

  • XML屬性列表(plist)歸檔

  • Preference(偏好設置)

  • NSKeyedArchiver歸檔(NSCoding)

  • SQLite3

  • Core Data

XML屬性列表(plist)歸檔

屬性列表是一種XML格式的文件,拓展名為plist。

如果對象是NSString、NSDictionary、NSArray、NSData、NSNumber等類型,就可以使用writeToFile:atomically:方法直接將對象寫到屬性列表文件中。

舉個例子:將一個NSDictionary對象歸檔到一個plist屬性列表中

// 將數據封裝成字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@"母雞" forKey:@"name"];
[dict setObject:@"15013141314" forKey:@"phone"];
[dict setObject:@"27" forKey:@"age"];
// 將字典持久化到Documents/stu.plist文件中
[dict writeToFile:path atomically:YES];

成功寫入到Documents目錄下:

1461840164876699.png

運行結果

用文本編輯器打開,文件內容為:

1353118-b741943dd4a08d67.jpg

文本編輯器查看

用xcode打開屬性文件:

1353118-79356fa27c45eed4.jpg

xcode查看

讀取屬性列表,恢復NSDictionary對象

// 讀取Documents/stu.plist的內容,實例化NSDictionary
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@"name:%@", [dict objectForKey:@"name"]);
NSLog(@"phone:%@", [dict objectForKey:@"phone"]);
NSLog(@"age:%@", [dict objectForKey:@"age"]);

1353118-b716578072700e39.jpg

輸出結果:

1353118-dc978e8864a6891f.jpg

屬性列表-NSDictionary的存儲和讀取過程

Preference(偏好設置)

很多iOS應用都支持偏好設置,比如保存用戶名、密碼、字體大小等設置,iOS提供了一套標准的解決方案來為應用加入偏好設置功能。

每個應用都有個NSUserDefaults實例,通過它來存取偏好設置。

比如,保存用戶名、字體大小、是否自動登錄

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"itcast" forKey:@"username"];
[defaults setFloat:18.0f forKey:@"text_size"];
[defaults setBool:YES forKey:@"auto_login"];

1353118-55bb258a3d05246d.jpg

存儲文件內容

NSKeyedArchiver歸檔(NSCoding)

如果對象是NSString、NSDictionary、NSArray、NSData、NSNumber等類型,可以直接用NSKeyedArchiver進行歸檔和恢復。

不是所有的對象都可以直接用這種方法進行歸檔,只有遵守了NSCoding協議的對象才可以。

NSCoding協議有2個方法:

  • encodeWithCoder:

每次歸檔對象時,都會調用這個方法。一般在這個方法裡面指定如何歸檔對象中的每個實例變量,可以使用encodeObject:forKey:方法歸檔實例變量

  • initWithCoder:

每次從文件中恢復(解碼)對象時,都會調用這個方法。一般在這個方法裡面指定如何解碼文件中的數據為對象的實例變量,可以使用decodeObject:forKey方法解碼實例變量

歸檔一個NSArray對象到Documents/array.archive

NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];
[NSKeyedArchiver archiveRootObject:array toFile:path];

歸檔成功

1353118-d2f19a707d722c16.jpg

存檔文件

恢復(解碼)NSArray對象

NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

1353118-391ffe7d0eca524e.jpg

存取過程

歸檔Person對象

Person.h

@interface Person : NSObject@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float height;
@end

Person.m

@implementation Person
- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeInt:self.age forKey:@"age"];
    [encoder encodeFloat:self.height forKey:@"height"];
}
- (id)initWithCoder:(NSCoder *)decoder {
    self.name = [decoder decodeObjectForKey:@"name"];
    self.age = [decoder decodeIntForKey:@"age"];
    self.height = [decoder decodeFloatForKey:@"height"];
    return self;
}
@end

歸檔(編碼)

Person *person = [[[Person alloc] init] autorelease];
person.name = @"hosea";
person.age = 22;
person.height = 1.83f;
[NSKeyedArchiver archiveRootObject:person toFile:path];

恢復(解碼)

Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

NSKeyedArchiver-歸檔對象的注意

- 如果父類也遵守了NSCoding協議,請注意:
    - 應該在encodeWithCoder:方法中加上一句
    [super encodeWithCode:encode];
    確保繼承的實例變量也能被編碼,即也能被歸檔
    - 應該在initWithCoder:方法中加上一句
    self = [super initWithCoder:decoder];
    確保繼承的實例變量也能被解碼,即也能被恢復

歸檔NSData

使用archiveRootObject:toFile:方法可以將一個對象直接寫入到一個文件中,但有時候可能想將多個對象寫入到同一個文件中,那麼就要使用NSData來進行歸檔對象。

NSData可以為一些數據提供臨時存儲空間,以便隨後寫入文件,或者存放從磁盤讀取的文件內容。可以使用[NSMutableData data]創建可變數據空間。

1353118-4ed13bedff51f5d4.jpg

原理

舉個例子:NSData-歸檔2個Person對象到同一文件中

歸檔(編碼)

// 新建一塊可變數據區
NSMutableData *data = [NSMutableData data];
// 將數據區連接到一個NSKeyedArchiver對象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// 開始存檔對象,存檔的數據都會存儲到NSMutableData中
[archiver encodeObject:person1 forKey:@"person1"];
[archiver encodeObject:person2 forKey:@"person2"];
// 存檔完畢(一定要調用這個方法,調用了這個方法,archiver才會將encode的數據存儲到NSMutableData中)
[archiver finishEncoding];
// 將存檔的數據寫入文件
[data writeToFile:path atomically:YES];

恢復(解碼)

// 從文件中讀取數據
NSData *data = [NSData dataWithContentsOfFile:path];
// 根據數據,解析成一個NSKeyedUnarchiver對象
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
Person *person1 = [unarchiver decodeObjectForKey:@"person1"];
Person *person2 = [unarchiver decodeObjectForKey:@"person2"];
// 恢復完畢(這個方法調用之後,unarchiver不能再decode對象,而且會通知unarchiver的代理調用unarchiverWillFinish:和unarchiverDidFinish:方法)
[unarchiver finishDecoding];

PS:也可將多個對象放入到一個數組中。

  • 將數組進行歸檔,在數組對象執行archiveRootObject:toFile時,數組中每個對象會自動調用encodeWithCoder:方法進行歸檔;

  • 相反數組文件進行解檔時,在數組對象執行unarchiveObjectWithFile:時,數組中每個對象會自動調用initWithCoder:方法進行解檔。

利用歸檔實現深復制

比如對一個Person對象進行深復制

// 臨時存儲person1的數據
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
// 解析data,生成一個新的Person對象
Person *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 分別打印內存地址
NSLog(@"person1:0x%x", person1); // person1:0x7177a60
NSLog(@"person2:0x%x", person2); // person2:0x7177cf0

1353118-ffc98002136a4b44.jpg

深復制原理

SQLite3

SQLite3簡介

SQLite3是一款開源的嵌入式關系型數據庫,可移植性好、易使用、內存開銷小。

SQLite3是無類型的,意味著你可以保存任何類型的數據到任意表的任意字段中。比如下列的創表語句是合法的:

create table t_person(name, age);

為了保證可讀性,建議還是把字段類型加上:

create table t_person(name text, age integer);

SQLite3常用的5種數據類型:text、integer、float、boolean、blob

在iOS中使用SQLite3,首先要添加庫文件libsqlite3.dylib和導入主頭文件

1353118-d7cd70cf7fca9851.jpg

導入庫

創建或打開數據庫

// path為:~/Documents/person.db
sqlite3 *db;
int result = sqlite3_open([path UTF8String], &db);

代碼解析:

  • sqlite3_open()將根據文件路徑打開數據庫,如果不存在,則會創建一個新的數據庫。如果result等於常量SQLITE_OK,則表示成功打開數據庫

  • sqlite3 *db:一個打開的數據庫實例

  • 數據庫文件的路徑必須以C字符串(而非NSString)傳入

關閉數據庫:sqlite3_close(db);

執行創表語句

char *errorMsg;  // 用來存儲錯誤信息
char *sql = "create table if not exists t_person(id integer primary key autoincrement, name text, age integer);";
int result = sqlite3_exec(db, sql, NULL, NULL, &errorMsg);

代碼解析:

  • sqlite3_exec()可以執行任何SQL語句,比如創表、更新、插入和刪除操作。但是一般不用它執行查詢語句,因為它不會返回查詢到的數據

  • sqlite3_exec()還可以執行的語句:

  • 開啟事務:begin transaction;

  • 回滾事務:rollback;

  • 提交事務:commit;

帶占位符插入數據

char *sql = "insert into t_person(name, age) values(?, ?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, "母雞", -1, NULL);
    sqlite3_bind_int(stmt, 2, 27);
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
    NSLog(@"插入數據錯誤");
}
sqlite3_finalize(stmt);

代碼解析:

  • sqlite3_prepare_v2()返回值等於SQLITE_OK,說明SQL語句已經准備成功,沒有語法問題

  • sqlite3_bind_text():大部分綁定函數都只有3個參數

  • 第1個參數是sqlite3_stmt *類型

  • 第2個參數指占位符的位置,第一個占位符的位置是1,不是0

  • 第3個參數指占位符要綁定的值

  • 第4個參數指在第3個參數中所傳遞數據的長度,對於C字符串,可以傳遞-1代替字符串的長度

  • 第5個參數是一個可選的函數回調,一般用於在語句執行後完成內存清理工作

  • sqlite_step():執行SQL語句,返回SQLITE_DONE代表成功執行完畢

  • sqlite_finalize():銷毀sqlite3_stmt *對象

查詢數據

char *sql = "select id,name,age from t_person;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        int _id = sqlite3_column_int(stmt, 0);
        char *_name = (char *)sqlite3_column_text(stmt, 1);
        NSString *name = [NSString stringWithUTF8String:_name];
        int _age = sqlite3_column_int(stmt, 2);
        NSLog(@"id=%i, name=%@, age=%i", _id, name, _age);
    }
}
sqlite3_finalize(stmt);

代碼解析

  • sqlite3_step()返回SQLITE_ROW代表遍歷到一條新記錄

  • sqlite3_column_*()用於獲取每個字段對應的值,第2個參數是字段的索引,從0開始

Core Data

Core Data簡單介紹

  • Core Data框架提供了對象-關系映射(ORM)的功能,即能夠將OC對象轉化成數據,保存在SQLite3數據庫文件中,也能夠將保存在數據庫中的數據還原成OC對象。在此數據操作期間,不需要編寫任何SQL語句。使用此功能,要添加CoreData.framework和導入主頭文件CoreData/CoreData.h。

1353118-801e5c3548ba5238.jpg

對象-關系映射

  • 在Core Data,需要進行映射的對象稱為實體(entity),而且需要使用Core Data的模型文件來描述應用的所有實體和實體屬性

這裡以Person和Card(身份證)2個實體為例子,先看看實體屬性和之間的關聯關系

1353118-bcb9d93c189ca107.jpg

實體屬性和之間的關聯關系

  • Person中有個Card屬性,Card中有個Person屬性。

  • 屬於一對一雙向關聯。

模型文件

創建文件

1353118-847db2b702ae2142.jpg

添加實體

1353118-8e522e29273d4c03.png

添加Person實體的基本屬性

1353118-3c28a1adca613a50.jpg

添加Card實體的基本屬性

1353118-e5bc2b8fa5ccce37.jpg

在Person中添加card屬性

1353118-816276353724915a.jpg

在Card中添加person屬性

1353118-8bb511e4742b94f7.jpg

NSManagedObject

  • 通過Core Data從數據庫取出的對象,默認情況下都是NSManagedObject對象

  • NSManagedObject的工作模式有點類似於NSDictionary對象,通過鍵-值對來存取所有的實體屬性

  • setValue:forKey: 存儲屬性值(屬性名為key)

  • valueForKey: 獲取屬性值(屬性名為key)

1353118-21be3c3c51f728ac.jpg

Core Data主要對象

1353118-e79a0387a70eb1cb.jpg

搭建Core Data上下文環境

從應用程序包中加載模型文件

NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];

傳入模型,初始化NSPersistentStoreCoordinator

NSPersistentStoreCoordinator *psc = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model] autorelease];

構建SQLite文件路徑

NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSURL *url = [NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"person.data"]];

添加持久化存儲庫,這裡使用SQLite作為存儲庫

NSError *error = nil;
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
if (store == nil) { // 直接拋異常
  [NSException raise:@"添加數據庫錯誤" format:@"%@", [error localizedDescription]];
}

初始化上下文,設置persistentStoreCoordinator屬性

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = psc;
// 用完之後,還是要[context release];

添加數據

傳入上下文,創建一個Person實體對象

NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];

設置簡單屬性

[person setValue:@"hosea" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:22] forKey:@"age"];

傳入上下文,創建一個Card實體對象

NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
[card setValue:@"4414241933432" forKey:@"no"];

設置Person和Card之間的關聯關系

[person setValue:card forKey:@"card"];

利用上下文對象,將數據同步到持久化存儲庫

NSError *error = nil;
BOOL success = [context save:&error];
if (!success) {
  [NSException raise:@"訪問數據庫錯誤" format:@"%@", [error localizedDescription]];
}
//如果是想做更新操作:只要在更改了實體對象的屬性後調用[context save:&error],就能將更改的數據同步到數據庫

查詢數據

初始化一個查詢請求

NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];

設置要查詢的實體

NSEntityDescription *desc = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];

設置排序(按照age降序)

NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];

設置條件過濾(name like '%hosea-1%')

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*hosea-1*"];
request.predicate = predicate;

執行請求

NSError *error = nil;
NSArray *objs = [context executeFetchRequest:request error:&error];
if (error) {
  [NSException raise:@"查詢錯誤" format:@"%@", [error localizedDescription]];
}

遍歷數據

for (NSManagedObject *obj in objs) {
  NSLog(@"name=%@", [obj valueForKey:@"name"]);
}

刪除數據

傳入需要刪除的實體對象

[context deleteObject:managedObject];

將結果同步到數據庫

NSError *error = nil;
[context save:&error];
if (error) {
  [NSException raise:@"刪除錯誤" format:@"%@", [error localizedDescription]];
}

打開Core Data的SQL日志輸出開關

1353118-5d4b45107f5a7280.jpg

Core Data的延遲加載

  • Core Data不會根據實體中的關聯關系立即獲取相應的關聯對象

  • 比如通過Core Data取出Person實體時,並不會立即查詢相關聯的Card實體;當應用真的需要使用Card時,才會查詢數據庫,加載Card實體的信息

創建NSManagedObject的子類

默認情況下,利用Core Data取出的實體都是NSManagedObject類型的,能夠利用鍵-值對來存取數據

但是一般情況下,實體在存取數據的基礎上,有時還需要添加一些業務方法來完成一些其他任務,那麼就必須創建NSManagedObject的子類

1353118-0ae824488f87d4c7.jpg

選擇模型文件

1353118-ad48dc961b0dc63b.jpg

選擇需要創建子類的實體

1353118-7d62d41d5e888d25.jpg

1353118-c2e164c1d17cd6ee.jpg

那麼生成一個Person實體對象就應該這樣寫

Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
person.name = @"hosea";
person.age = [NSNumber numberWithInt:22];
Card *card = [NSEntityDescription insertNewObjectForEntityForName:@”Card" inManagedObjectContext:context];
card.no = @”4414245465656";
person.card = card;
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved