沙盒
iOS程序默認情況下只能訪問自己的目錄,這個目錄被稱作沙盒
沙盒結構
沙盒結構主要為
Document
Library/Caches
Library/Preferences
tmp
目錄特性
應用程序包:
存放程序的資源文件和可執行文件
NSString *path = [[NSBundle mainBundle] bundlePath];
Document:
最常用的目錄,iTunes備份會備份這個文件夾,適合保存重要的數據
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectories,NSUserDomainMask,YES).firstObject;
Library/Caches:
iTunes不會備份這個文件夾,適合存放不需要備份的非重要的數據
NSString *path = NSSearchPathForDirectoriesInDomains(NSCacheDirectories,NSUserDomainMask,YES).firstObject;
Library/Preferences:
iTunes備份的時候會保存這個文件夾,適合存放用戶的偏好設置,程序的設置信息
tmp:
臨時文件,iTunes不會備份
NSString *path = NSTemporatyDirectory();
方案:
1、寫入plist文件
2、NSUserDefault存儲
3、歸檔
4、core data
5、sqlite
1)plist
plist通過XML的形式保存在目錄中,只能存儲以下類型的值:
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
獲取路徑:
NSString *documentPath = NSSearchPathForDirectotiesInDomains(NSDocumentDirectoty,NSUserDomainMask,YES).firstObject;
NSString *path = [doucumentPath stringByAppendingPathComponent:@"data.plist"];
存儲
NSArray *array = @[@"12",@"11",@"10"];
//atomically表示是否需要先寫入一個輔助文件,再把輔助文件拷貝到目標文件地址。這是更安全的寫入文件方法,一般都寫YES
[array writeToFile:path automically:YES];
讀取
NSArray *arr = [array arrayWithContentsOfFile:path];
2)NSUserDefault
用法
NSUserDefault *defualt = [NSUserDefault standUserDefault];
//存儲
[default setObjet:@"object1" forKey:@"object1"];
//存儲之后應立即同步,防止數據丟失
[default synchronize];
//讀取
NSString *object = default[@"object1"];
tip:
- 偏好設置是專門用來保存應用程序的配置信息的,一般不要在偏好設置中保存其他數據。
- 如果沒有調用synchronize方法,系統會根據I/O情況不定時刻地保存到文件中。所以如果需要立即寫入文件的就必須調用synchronize方法。
- 偏好設置會將所有數據保存到同一個文件中。即preference目錄下的一個以此應用包名來命名的plist文件。
3)NSKeyedAchiever歸檔
1、遵循NSCoding協議,實現協議方法
@interface Person : NSObject<NSCoding>
@property (strong, nonatomic) UIImage *avatar;
@end
2、實現協議方法
//歸檔
-(void) encodeWithCoder:(NSCoder *)aCoder{
[aCoder encoderObject:self.avator forKey:@"avator"];
}
//解檔
-(id) initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if self {
self.avator = [aDecoder decoderObjectForKey:@"avator"];
}
return self;
}
注意:
如果需要歸檔的類是某個自定義類的子類時,就需要在歸檔和解檔之前先實現父類的歸檔和解檔方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;
3、存入數據
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectories,NSUserDomainMask,YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [Person new];
person.avator = self.avator;
[NSkeyedArchiver archiveRootObject:person toFile:path];
4、讀取數據
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectories,NSUserDomainMask,YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedArchiver unarchiveObjectWithFile:path];
if person{
self.avator = person.avator;
}
注意:前面三種存儲方式都是覆蓋存儲,每次需要存儲數據的時候都必須把所有的數據都讀取出來,修改數據后再覆蓋寫入,不適用于大文件的存儲。
4)CoreData
CoreData能減少50%~70%model層的代碼量,實現model層的代碼高效復用。
Core Data typically decreases by 50 to 70 percent the amount of code you write to support the model layer.
通過建立實體的屬性、關系表格,能夠實現數據一對多,多對多的對應關系,使增刪改查更加高效。
新建一個demo工程,簡單實現CoreData的增刪改查功能:
新建工程,勾選Use Core Data
新建工程之后會看到一個.xcdatamodel的文件,選中它,在左下方添加一個實體文件,然后添加屬性
新建一個file,選擇 Core Data / NSManagedObjext Subclass,之后選中. xcdatamodel對象的文件名,選擇剛創建的對應實體
之后會得到一個Person的model文件,包含剛才添加的兩個屬性:name和age。
coredata的增刪改查都依賴于NSManagedObjectContext的實例。
創建NSManagedObjectContext實例
NSURL *URL = [[NSBundle mainBundle] URLForResource:@"CoreDataDemo" withExtension:@"mood"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentOfURL:URL];
//獲取database文件路徑
NSURL *storeURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSPersistentStoreCoordinate *persistent = [[NSPersistentStoreCoordinate alloc] initWithManageObjectModel:model];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentCoordinate:persistent];
插入:
//創建模型
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.context];
person.name = [NSString stringWithFormat:@"name%d",arc4random()];
person.age = @8;
NSError *error;
//保存
[self.context save:&error];
刪除:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
//檢索條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@",@"hah"];
[request setPredicate:predicate];
NSArray *arr = [self.context executeFetchRequest:request error:nil];
if (arr.count && arr.count > 0) {
for (Person *p in arr) {
[self.context deleteObject:p];
}
[self.context save:nil];
}else {
NSLog(@"nodata");
}
查詢:
//獲取實體
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context];
//創建請求
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
//獲取所有的Person實體的對象
NSError *error;
NSArray *arr = [self.context executeFetchRequest:request error:&error];
for (Person *p in arr) {
NSLog(p.name);
}
修改:
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@",@"hah1"];
[request setPredicate:predicate];
//搜索符合條件的對象
NSArray *arr = [self.context executeFetchRequest:request error:nil];
for (Person *p in arr) {
p.name = @"hah";
}
[self.context save:nil];
SQLite3
表面上SQLite將數據分為以下幾種類型:
integer:整數
real:實數
text:文本
blob:二進制數據,圖片,文件等
實際上,SQLite能存儲所有的數據。使用SQLite,要先添加libsqlite3.dylib庫文件并導入頭文件。
在操作數據庫之前,首先要先打開數據庫文件,然后新建或者指定一張表。
建表:
/*
打開數據庫并創建一張表
*/
-(void)openDatabase{
//設置文件名
NSString *path = [NSSearchPathForDirectoriesInDomain(NSDocumentDirectory,NSUserDomainMask,YES).lastObject stringByAppendingPathComponent:@"person.db"];
//打開數據庫文件,如果沒有會自動創建一個。
NSInteger result = sqlite3_open(path.UTF8String, &sqlite);
if (result == SQLITE_OK) {
NSLog(@"打開數據庫文件");
//建表
char *errmsg = NULL;
sqlite3_exec(sqlite, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)", NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"建表成功");
}else{
NSLog(@"建表失敗");
}
}else{
NSLog(@"打開數據庫文件失敗");
}
}
sqlite_exec()方法可以做很多操作,創表、增刪改都行,但是一般不用于查詢操作,因為sqlite_exec()不會返回查詢的結果,而查詢操作是要求返回查詢結果的。
插入數據:
- (void)insertData{
NSString *name = @"sqlite insert data";
NSInteger count = 40;
NSString *operation = [NSString stringWithFormat:@"INSERT INTO t_person(name,age) VALUE('%@','%ld',)",name,count];
sqlite3_exec(sqlite, operation.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {
NSLog(@"錯誤:%s",errmsg);
}else{
NSLog(@"插入成功");
}
}
讀取數據
- (void)readData {
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1000];
char *sql = "select name, age from t_person;";
sqlite3_stmt *stmt;
NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, -1, &stmt, NULL);
if (result == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
char *name = (char *)sqlite3_column_text(stmt, 0);
NSInteger age = sqlite3_column_int(stmt, 1);
//創建對象
Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
[mArray addObject:person];
}
self.dataList = mArray;
}
sqlite3_finalize(stmt);
}
開發過程中,SQLite的操作都使用第三方開源庫FMDB,它是用OC封裝了sqlite的API,相對于用c語言函數使用sqlite3更加容易理解,也更輕量級。
FMDB
FMDB使用起來更加面向對象,省去了很多冗余的C語言代碼,對比CoreData更加輕量級,更靈活,同時也提供了多線程安全的數據庫操作方法,有效防止數據混亂。
核心類
- FMDatabase:
一個FMDatabase對象就代表一個單獨的數據庫,用來執行sqlite語句。
- FMRequestSet
用來存儲執行查詢后的結果集合
- FMDatabaseQueue
用于在多線程中進行查詢和更新,它是線程安全的。
簡單使用
和sqlite3一樣,使用FMDB需要先打開數據庫,創建一張表。
FMDB中的除了查詢外的所有操作都可以用executeUpdate...方法來操作,比如,插入、刪除、更改。
//常用方法:
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
1、創建一個庫
NSString *filePath = [NSSearchPathForDirectoriesInDomain(NSDocumentDirectory,NSUSerDomainmasks,YES).lastObject stringByAppendingPathComponent:@"person.db"];
//打開數據之前先清空舊的數據庫
NSFileManager *manager = [NSFileManeger defaultManager];
[manager removeItemAtPath:filePath error:nil];
//打開數據庫
FMDatabase *databse = [FMDatabase databaseWithPath:filePath];
//判斷是否成功打開數據庫
if (![databse open]){
NSLog(@"打開數據庫失敗");
}
傳入的path可以有以下三種情況,FMDatabse會根據傳入的path值做不同的處理
1、具體文件路徑:路徑存在直接打開,不存在會自動創建
2、空字符串@"":創建一個臨時目錄下的的數據庫文件,當數據庫連接關閉時,數據庫文件會被銷毀
3、nil:創建內存中的臨時數據庫文件,當數據庫連接關閉時,數據庫文件銷毀
2、創建表格
[database executeUpdate:@"CREATE TABLE IF NOT EXIST t_person(id integer primary key autoincrement,name text,age integer)"];
3、插入
事務處理有開始,結束,回滾三種。
如:向一個主表和子表插入數據時。
如果順利不出錯在insert(主)前開始事務,在insert(子)后
結束事務。出錯的話他就會回滾到insert前的狀態。
如正常的寫法:
{
1、開始事務
2、執行操作
。。。。
3、結束事務;
回滾事務
執行提示等一些信息
。。。
}
FMDB中開始事務:beginTransaction,結束事務:commit,
[database beginTransaction];
[database executeUpdate:@"INSERT INTO t_person(name,age) VALUE(?,?)", @"laowang",[NSNumber numberWithInteger56]];
[databae commit];
4、查詢
FMRequestSet *requestSet = [database executeQuery:@"SELECT * FROM t_person"];
while([requestSet next]){
NSString *name = [requestSet stringForColum:@"name"];
NSInteger age = [requestSet intForColum:@"age"];
}
[requestSet close];
5、線程安全
當多個線程同時對一個數據操作時容易造成數據的丟失損壞,因此不要讓多個線程同時分享同一個FMDatabase實例。使用FMDatabaseQueue來保證不造成數據混亂,它是線程安全的。
創建隊列:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseWithPath:filePath];
//在隊列中操作數據庫
[queue inDatabase:^(FMDatabase *database){
[database executeUpdate:@"CREATE TABLE IF NOT EXIST t_person(id integer autoincrements,name text ,age integer)"];
[database executeUpdate:@"INSERT INTO t_person(name,age) VALUE('laozhang',?)",[NSNumber numberOfInt:35];
FMResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
}];
還能把任務輕松的包裝好放到事務中
[queue inTransaction:^(FMDatabase *database, BOOL *rollback) {
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];
FMResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
//回滾
*rollback = YES;
}];