最全iOS資料儲存方法介紹:FMDB,SQLite3 ,Core Data,Plist,Preference偏好設定,NSKeyedArchiver歸檔,Realm
專案準備運用的Core Data進行本地資料儲存,本來打算只寫一下Core Data的,不過既然說到了資料儲存,乾脆來個資料儲存基礎大總結!
本文將對以下幾個模組進行敘述。
- 沙盒
- Plist
- Preference偏好設定
- NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔
- SQLite3的使用
- FMDB
- Core Data
Realm的文件很詳細,這篇文章就不寫了: Realm的使用方法
下圖是Core Data堆疊的圖示,在這裡是為了做文章的封面圖片,後文會介紹Core Data的使用方法。
Core Data
一、沙盒
iOS本地化儲存的資料儲存在沙盒中, 並且每個應用的沙盒是相對獨立的。每個應用的沙盒檔案結構都是相同的,如下圖所示:
沙盒目錄
Documents:iTunes會備份該目錄。一般用來儲存需要持久化的資料。
Library/Caches:快取,iTunes不會備份該目錄。記憶體不足時會被清除,應用沒有執行時,可能會被清除,。一般儲存體積大、不需要備份的非重要資料。
Library/Preference:iTunes同會備份該目錄,可以用來儲存一些偏好設定。
tmp
: iTunes不會備份這個目錄,用來儲存臨時資料,應用退出時會清除該目錄下的資料。
如何拿到每個資料夾的路徑呢?我這裡就只說最好的一種方法。
// 這個方法返回的是一個數組,iOS中這個陣列其實只有一個元素,所以我們可以用lastObject或lastObject來拿到Documents目錄的路徑 NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; // 得到Document目錄下的test.plist檔案的路徑 NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
上面的方法有三個引數,這裡分別說一下。
NSDocumentDirectory: 第一個引數代表要查詢哪個檔案,是一個列舉,點進去看一下發現有很多選擇,為了直接找到沙盒中的Documents目錄,我們一般用NSDocumentDirectory。
NSUserDomainMask: 也是一個列舉,表示搜尋的範圍限制於當前應用的沙盒目錄。我們一般就選擇NSUserDomainMask。
YES (expandTilde): 第三個引數是一個BOOL值。iOS中主目錄的全寫形式是/User/userName,這個引數填YES就表示全寫,填NO就是寫成‘‘~’’,我們一般填YES。
根據上面的文字,你應用可以知道如何拿到Library/Caches目錄下的檔案路徑了吧?沒錯,就是這樣:
//獲取Library/Caches目錄路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
//獲取Library/Caches目錄下的test.data檔案路徑
NSString *filePath = [path stringByAppendingPathComponent:@"test.data"];
//獲取temp路徑
NSString *tmp= NSTemporaryDirectory();
//獲取temp下test.data檔案的路徑
NSString *filePath = [tmp stringByAppendingPathComponent:@"test.data"];
所以,如果程式中有需要長時間持久化的資料,就選擇Documents,如果有體積大但是並不重要的資料,就可以選擇交給Library,而臨時沒用的資料當然是放到temp。至於Preference則可以用來儲存一些設定類資訊,後面會講到偏好設定的使用方法。
推薦一個好用的分類工具集合WHKit,可以直接使用分類方法拿到檔案路徑。
//一行程式碼搞定
NSString *filePath = [@"test.plist" appendDocumentPath];
//使用WHKit,上面這一行程式碼等同於下面這兩行。
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
二、Plist儲存
Plist檔案的Type可以是字典NSDictionary或陣列NSArray,也就是說可以把字典或陣列直接寫入到檔案中。
NSString、NSData、NSNumber等型別,也可以使用writeToFile:atomically:方法直接將物件寫入檔案中,只是Type為空。
下面就舉個例子來看一下如何使用Plist來儲存資料。
//準備要儲存的資料
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"first" forKey:@"1"];
//獲取路徑,你也可以利用上面說WHKit更優雅的拿到路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
//寫入資料
[dict writeToFile:filePath atomically:YES];
上面的程式碼執行之後,在應用沙盒的Documents中就建立了一個plist檔案,並且已經寫入資料儲存。
寫入plist
資料儲存了,那麼如何讀取呢?
//拿到plist檔案路徑,你也可以利用上面說WHKit更優雅的拿到路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
//解析資料,log出的結果為first,讀取成功
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *result = dict[@"1"];
NSLog(@"%@",result);
上面這段程式碼就讀出了plist種的資料。
三、Preference偏好設定
偏好設定的使用非常方便快捷,我們一般使用它來進行一些設定的記錄,比如使用者名稱,開關是否開啟等設定。
Preference是通過NSUserDefaults來使用的,是通過鍵值對的方式記錄設定。下面舉個例子。
利用NSUserDefaults判斷APP是不是首次啟動。
// 啟動的時候判斷key有沒有value,如果有,說明已經啟動過了,如果沒有,說明是第一次啟動
if (![[NSUserDefaults standardUserDefaults] valueForKey:@"first"]) {
//如果是第一次啟動,就運用偏好設定給key設定一個value
[[NSUserDefaults standardUserDefaults] setValue:@"start" forKey:@"first"];
NSLog(@"是第一次啟動");
} else {
NSLog(@"不是第一次啟動");
}
通過鍵值對的方式非常easy的儲存了資料。
注意:NSUserDefaults可以儲存的資料型別包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要儲存其他型別,則需要轉換為前面的型別,才能用NSUserDefaults儲存。
下面的例子是用NSUserDefaults儲存圖片,需要先把圖片轉換成NSData型別。
UIImage *image=[UIImage imageNamed:@"photo"];
//UIImage物件轉換成NSData
NSData *imageData = UIImageJPEGRepresentation(image, 100);
//偏好設定可以儲存NSData,但是不能儲存UIimage
[[NSUserDefaults standardUserDefaults] setObject:imageData forKey:@"image"];
// 讀出data
NSData *getImageData = [[NSUserDefaults standardUserDefaults] dataForKey:@"image"];
//NSData轉換為UIImage
UIImage *Image = [UIImage imageWithData:imageData];
NSUserDefaults是不是很好用!不過有沒有覺得每次都寫[NSUserDefaults standardUserDefaults]有點煩,那麼又到了推薦環節,好用的分類工具WHKit,這個工具中有許多好用的巨集定義,其中一個就是KUSERDEFAULT,等同於[NSUserDefaults standardUserDefaults]。
WHKit中還有更多好用的巨集定義等著你!
四、NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔
歸檔和解檔會在寫入、讀出資料之前進行序列化、反序列化,資料的安全性相對高一些。
NSKeyedArchiver可以有三個使用情景。
1.對單個簡單物件進行歸檔/解檔
與plist差不多,對於簡單的資料進行歸檔,直接寫入檔案路徑。
//獲取歸檔檔案路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];
//對字串@”test”進行歸檔,寫入到filePath中
[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];
//根據儲存資料的路徑filePath解檔資料
NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
//log結構為@”test”,就是上面歸檔的資料
NSLog(@"%@",result);
當然,也可以儲存NSArray,NSDictionary等物件。
2.對多個物件進行歸檔/解檔
這種情況可以一次儲存多種不同型別的資料,最終使用的是與plist相同的writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile來寫入資料。
//獲取歸檔路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];
//用來承載資料的NSMutableData
NSMutableData *data = [[NSMutableData alloc] init];
//歸檔物件
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
//將要被儲存的三個資料
NSString *name = @"jack";
int age = 17;
double height = 1.78;
//運用encodeObject:方法歸檔資料
[archiver encodeObject:name forKey:@"name"];
[archiver encodeInt:age forKey:@"age"];
[archiver encodeDouble:height forKey:@"height"];
//結束歸檔
[archiver finishEncoding];
//寫入資料(儲存資料)
[data writeToFile:filePath atomically:YES];
//NSMutableData用來承載解檔出來的資料
NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
//解檔物件
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];
//分別解檔出三個資料
NSString *resultName = [unArchiver decodeObjectForKey:@"name"];
int resultAge = [unArchiver decodeIntForKey:@"age"];
double resultHeight = [unArchiver decodeDoubleForKey:@"height"];
//結束解檔
[unArchiver finishDecoding];
//成功打印出結果,說明成功歸檔解檔
NSLog(@"name = %@, age = %d, height = %.2f",resultName,resultAge,resultHeight);
3.歸檔儲存自定義物件
我認為這是歸檔最常使用的情景。
定義一個Person類,如果想對person進行歸檔解檔,首先要讓Person遵守<NSCoding>協議。
/********Person.h*********/
#import <Foundation/Foundation.h>
//遵守NSCoding協議
@interface Person : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
//自定義的歸檔儲存資料的方法
+(void)savePerson:(Person *)person;
//自定義的讀取沙盒中解檔出的資料
+(Person *)getPerson;
@end
NSCoding協議有2個方法:
- (void)encodeWithCoder:(NSCoder *)aCoder
歸檔時呼叫這個方法,在方法中使用encodeObject:forKey:歸檔變數。- (instancetype)initWithCoder:(NSCoder *)aDecoder
解檔時呼叫這個方法,在方法中石油decodeObject:forKey讀出變數。
/******Person.m*******/
#import "Person.h"
@implementation Person
//歸檔,Key建議使用巨集代替,這裡就不使用了
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
//解檔
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self=[super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
//類方法,運用NSKeyedArchiver歸檔資料
+(void)savePerson:(Person *)person {
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
[NSKeyedArchiver archiveRootObject:person toFile:path];
}
//類方法,使用NSKeyedUnarchiver解檔資料
+(Person *)getPerson {
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return person;
}
@end
下面就可以在需要的地方歸檔或解檔Person物件。
/*******ViewController.m*******/
//建立Person物件
Person *person = [Person new];
person.name = @"jack";
person.age = 17;
//歸檔儲存資料
[Person savePerson:person];
//解檔拿到資料
Person *resultPerson = [Person getPerson];
//打印出結果,證明歸檔解檔成功
NSLog(@"name = %@, age = %ld",resultPerson.name,resultPerson.age);
五、SQLite3的使用
1、首先需要新增庫檔案libsqlite3.0.tbd
2、匯入標頭檔案#import <sqlite3.h>
3、開啟資料庫
4、建立表
5、對資料表進行增刪改查操作
6、關閉資料庫
上程式碼之前,有些問題你需要了解。
-
SQLite 不區分大小寫,但也有需要注意的地方,例如GLOB 和 glob 具有不同作用。
-
SQLite3有5種基本資料型別 text、integer、float、boolean、blob
-
SQLite3是無型別的,在建立的時候你可以不宣告欄位的型別,不過還是建議加上資料型別
create table t_student(name, age);
create table t_student(name text, age integer);
下面的程式碼就是SQLite3的基本使用方法,帶有詳細註釋。
程式碼中用到了一個Student類,這個類有兩個屬性name和age。
/****************sqliteTest.m****************/
#import "sqliteTest.h"
//1.首先匯入標頭檔案
#import <sqlite3.h>
//2.資料庫
static sqlite3 *db;
@implementation sqliteTest
/** 3.開啟資料庫 */
+ (void)openSqlite {
//資料庫已經開啟
if (db != nil) {
NSLog(@"資料庫已經開啟");
return;
}
//建立資料檔案路徑
NSString *string = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *path = [string stringByAppendingPathComponent:@"Student.sqlite"];
NSLog(@"%@",path);
//開啟資料庫
int result = sqlite3_open(path.UTF8String, &db);
if (result == SQLITE_OK) {
NSLog(@"資料庫開啟成功");
} else {
NSLog(@"資料庫開啟失敗");
}
}
/** 4.建立表 */
+ (void)createTable {
//建立表的SQLite語句,其中id是主鍵,not null 表示在表中建立紀錄時這些欄位不能為NULL
NSString *sqlite = [NSString stringWithFormat:@"create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)"];
//用來記錄錯誤資訊
char *error = NULL;
//執行SQLite語句
int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
if (result == SQLITE_OK) {
NSLog(@"建立表成功");
}else {
NSLog(@"建立表失敗");
}
}
/** 5.新增資料 */
+ (void)addStudent:(Student *)stu {
//增添資料的SQLite語句
NSString *sqlite = [NSString stringWithFormat:@"insert into t_student (name,age) values ('%@','%ld')",stu.name,stu.age];
char *error = NULL;
int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
if (result == SQLITE_OK) {
NSLog(@"新增資料成功");
} else {
NSLog(@"新增資料失敗");
}
}
/** 6.刪除資料 */
+ (void)deleteStuWithName:(NSString *)name {
//刪除特定資料的SQLite語句
NSString *sqlite = [NSString stringWithFormat:@"delete from t_student where name = '%@'",name];
char *error = NULL;
int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
if (result == SQLITE_OK) {
NSLog(@"刪除資料成功");
} else {
NSLog(@"刪除資料失敗");
}
}
/** 7.更改資料 */
+ (void)upDateWithStudent:(Student *)stu WhereName:(NSString *)name {
//更新特定欄位的SQLite語句
NSString *sqlite = [NSString stringWithFormat:@"update t_student set name = '%@', age = '%ld' where name = '%@'",stu.name,stu.age,name];
char *error = NULL;
int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
if (result == SQLITE_OK) {
NSLog(@"修改資料成功");
} else {
NSLog(@"修改資料失敗");
}
}
/** 8.根據條件查詢 */
+ (NSMutableArray *)selectWithAge:(NSInteger)age {
//可變陣列,用來儲存查詢到的資料
NSMutableArray *array = [NSMutableArray array];
//查詢所有資料的SQLite語句
NSString *sqlite = [NSString stringWithFormat:@"select * from t_student where age = '%ld'",age];
//定義一個stmt存放結果集
sqlite3_stmt *stmt = NULL;
//執行
int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
if (result == SQLITE_OK) {
NSLog(@"查詢成功");
//遍歷查詢到的所有資料,並新增到上面的陣列中
while (sqlite3_step(stmt) == SQLITE_ROW) {
Student *stu = [[Student alloc] init];
//獲得第1列的姓名,第0列是id
stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
//獲得第2列的年齡
stu.age = sqlite3_column_int(stmt, 2);
[array addObject:stu];
}
} else {
NSLog(@"查詢失敗");
}
//銷燬stmt,防止記憶體洩漏
sqlite3_finalize(stmt);
return array;
}
/** 9.查詢所有資料 */
+ (NSMutableArray *)selectStudent {
//可變陣列,用來儲存查詢到的資料
NSMutableArray *array = [NSMutableArray array];
//查詢所有資料的SQLite語句
NSString *sqlite = [NSString stringWithFormat:@"select * from t_student"];
//定義一個stmt存放結果集
sqlite3_stmt *stmt = NULL;
//執行
int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
if (result == SQLITE_OK) {
NSLog(@"查詢成功");
//遍歷查詢到的所有資料,並新增到上面的陣列中
while (sqlite3_step(stmt) == SQLITE_ROW) {
Student *stu = [[Student alloc] init];
//獲得第1列的姓名,第0列是id
stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
//獲得第2列的年齡
stu.age = sqlite3_column_int(stmt, 2);
[array addObject:stu];
}
} else {
NSLog(@"查詢失敗");
}
//銷燬stmt,防止記憶體洩漏
sqlite3_finalize(stmt);
return array;
}
/** 10.刪除表中的所有資料 */
+ (void)deleteAllData {
NSString *sqlite = [NSString stringWithFormat:@"delete from t_student"];
char *error = NULL;
int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
if (result == SQLITE_OK) {
NSLog(@"清除資料庫成功");
} else {
NSLog(@"清除資料庫失敗");
}
}
/** 11.刪除表 */
+ (void)dropTable {
NSString *sqlite = [NSString stringWithFormat:@"drop table if exists t_student"];
char *error = NULL;
int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
if (result == SQLITE_OK) {
NSLog(@"刪除表成功");
} else {
NSLog(@"刪除表失敗");
}
}
/** 12.關閉資料庫 */
+ (void)closeSqlite {
int result = sqlite3_close(db);
if (result == SQLITE_OK) {
NSLog(@"資料庫關閉成功");
} else {
NSLog(@"資料庫關閉失敗");
}
}
附上SQLite的基本語句
- 建立表:
create table if not exists 表名 (欄位名1, 欄位名2...);
create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)
- 增加資料:
insert into 表名 (欄位名1, 欄位名2, ...) values(欄位1的值, 欄位2的值, ...);
insert into t_student (name,age) values (@"Jack",@17);
- 根據條件刪除資料:
delete from 表名 where 條件;
delete from t_student where name = @"Jack";
- 刪除表中所有的資料:
delete from 表名
delete from t_student
- 根據條件更改某個資料:
update 表名 set 欄位1 = '值1', 欄位2 = '值2' where 欄位1 = '欄位1的當前值'
update t_student set name = 'lily', age = '16' where name = 'Jack'
- 根據條件查詢:
select * from 表名 where 欄位1 = '欄位1的值'
select * from t_student where age = '16'
- 查詢所有資料:
select * from 表名
select * from t_student
- 刪除表:
drop table 表名
drop table t_student
- 排序查詢:
select * from 表名 order by 欄位
select * from t_student order by age asc (升序,預設)
select * from t_student order by age desc (降序)
- 限制:
select * from 表名 limit 值1, 值2
select * from t_student limit 5, 10 (跳過5個,一共取10個數據)
SQLite3還有事務方面的使用,這裡就不做說明了,下面的FMDB中會有事務的使用。
六、FMDB
FMDB封裝了SQLite的C語言API,更加面向物件。
首先需要明確的是FMDB中的三個類。
FMDatabase:可以理解成一個數據庫。
FMResultSet:查詢的結果集合。
FMDatabaseQueue:運用多執行緒,可執行多個查詢、更新。執行緒安全。
FMDB基本語法
查詢:executeQuery: SQLite語句命令。
[db executeQuery:@"select id, name, age from t_person"]
其餘的操作都是“更新”:executeUpdate: SQLite語句命令。
// CREATE, UPDATE, INSERT, DELETE, DROP,都使用executeUpdte
[db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"]
FMDB的基本使用
在專案中匯入FMDB框架和sqlite3.0.tbd,匯入標頭檔案。
1. 開啟資料庫,並建立表
初始化FMDatabase:FMDatabase *db = [FMDatabase databaseWithPath:filePath];
其中的filePath是提前準備好要存放資料的路徑。
開啟資料庫:[db open]
建立資料表:[db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"];
#import "ViewController.h"
#import <FMDB.h>
@interface ViewController ()
@end
@implementation ViewController{
FMDatabase *db;
}
- (void)openCreateDB {
//存放資料的路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"person.sqlite"];
//初始化FMDatabase
db = [FMDatabase databaseWithPath:filePath];
//開啟資料庫並建立person表,person中有主鍵id,姓名name,年齡age
if ([db open]) {
BOOL success = [db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"];
if (success) {
NSLog(@"創表成功");
}else {
NSLog(@"建立表失敗");
}
} else {
NSLog(@"開啟失敗");
}
}
2. 插入資料
運用executeUpdate方法執行插入資料命令: [db executeUpdate:@"insert into t_person(name,age) values(?,?)",@"jack",@17]
-(void)insertData {
BOOL success = [db executeUpdate:@"insert into t_person(name,age) values(?,?)",@"jack",@17];
if (success) {
NSLog(@"新增資料成功");
} else {
NSLog(@"新增資料失敗");
}
}
3. 刪除資料
刪除姓名為lily的資料:[db executeUpdate:@"delete from t_person where name = 'lily'"]
-(void)deleteData {
BOOL success = [db executeUpdate:@"delete from t_person where name = 'lily'"];
if (success) {
NSLog(@"刪除資料成功");
} else {
NSLog(@"刪除資料失敗");
}
}
4. 修改資料
把年齡為17歲的資料,姓名改為lily:[db executeUpdate:@"update t_person set name = 'lily' where age = 17"]
-(void)updateData {
BOOL success = [db executeUpdate:@"update t_person set name = 'lily' where age = 17"];
if (success) {
NSLog(@"更新資料成功");
} else {
NSLog(@"更新資料失敗");
}
}
5. 查詢資料
執行查詢語句,用FMResultSet接收查詢結果:FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"]
遍歷查詢結果:[set next]
拿到每條數的姓名:NSString *name = [set stringForColumnIndex:1];
也可以這樣拿到每條資料的姓名:NSString *name = [result stringForColumn:@"name"];
FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"];
while ([set next]) {
int ID = [set intForColumnIndex:0];
NSString *name = [set stringForColumnIndex:1];
int age = [set intForColumnIndex:2];
NSLog(@"%d,%@,%d",ID,name,age);
}
}
6. 刪除表
刪除指定表:[db executeUpdate:@"drop table if exists t_person"]
-(void)dropTable {
BOOL success = [db executeUpdate:@"drop table if exists t_person"];
if (success) {
NSLog(@"刪除表成功");
} else {
NSLog(@"刪除表失敗");
}
}
FMDatabaseQueue基本使用
FMDatabase是執行緒不安全的,當FMDB資料儲存想要使用多執行緒的時候,FMDatabaseQueue就派上用場了。
初始化FMDatabaseQueue的方法與FMDatabase類似
//資料檔案路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
//初始化FMDatabaseQueue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
在FMDatabaseQueue中執行命令的時候也是非常方便,直接在一個block中進行操作
-(void)FMDdatabaseQueueFunction {
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//資料檔案路徑
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
//初始化FMDatabaseQueue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
//在block中執行SQLite語句命令
[dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
//建立表
[db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
//新增資料
[db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@17];
[db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"lily",@16];
//查詢資料
FMResultSet *set = [db executeQuery:@"select id, name, age from t_student"];
//遍歷查詢到的資料
while ([set next]) {
int ID = [set intForColumn:@"id"];
NSString *name = [set stringForColumn:@"name"];
int age = [set intForColumn:@"age"];
NSLog(@"%d,%@,%d",ID,name,age);
}
}];
}
FMDB中的事務
什麼是事務?
事務(Transaction)是不可分割的一個整體操作,要麼都執行,要麼都不執行。
舉個例子,幼兒園有20位小朋友由老師組織出去春遊,返校的時候,所有人依次登上校車,這時候如果有一位小朋友沒有上車,車也是不能出發的。所以哪怕19人都上了車,也等於0人上車。20人是一個整體。
當然這個例子可能不是很精準。
FMDB中有事務的回滾操作,也就是說,當一個整體事務在執行的時候出了一點小問題,則執行回滾,之後這套事務中的所有操作將整體無效。
下面程式碼中,利用事務迴圈向資料庫中新增2000條資料,假如在新增的過程中出現了一些問題,由於執行了*rollback = YES的回滾操作,資料庫中一個數據都不會出現。
如果第2000條資料的添加出了問題,哪怕之前已經添加了1999條資料,由於執行了回滾,資料庫中依然一個數據都沒有。
//資料庫路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
//初始化FMDatabaseQueue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
//FMDatabaseQueue的事務inTransaction
[dbQueue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
//建立表
[db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
//迴圈新增2000條資料
for (int i = 0; i < 2000; i++) {
BOOL success = [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@(i)];
//如果新增資料出現問題,則回滾
if (!success) {
//資料回滾
*rollback = YES;
return;
}
}
}];
七、Core Data
Core Data有著圖形化的操作介面,並且是操作模型資料的,更加面向物件。當你瞭解並熟悉它的時候,相信你會喜歡上這個資料庫儲存框架。
利用Core Data快速實現資料儲存
1. 圖形化建立模型
建立專案的時候,勾選下圖中的Use Core Data選項,工程中會自動建立一個數據模型檔案。當然,你也可以在開發中自己手動建立。
自動建立模型檔案
下圖就是自動創建出來的檔案
創建出來的檔案
如果沒有勾選,也可以在這裡手動建立。
手動建立
點選Add Entity之後,相當一張資料表。表的名稱自己在上方定義,注意首字母要大寫。
在介面中還可以為資料實體新增屬性和關聯屬性。
建立一個數據表
Core Data屬性支援的資料型別如下
資料型別
編譯之後,Xcode會自動生成Person的實體程式碼檔案,並且檔案不會顯示在工程中,如果下圖中右側Codegen選擇Manual/None,則Xcode就不會自動生成程式碼,我們可以自己手動生成。
6.png
手動生成實體類程式碼,選中CoreDataTest.xcdatamodeld檔案,然後在Mac選單欄中選擇Editor,如下圖所示。一路Next就可以了。
如果沒有選擇Manual/None,依然進行手動建立的話,則會與系統自動建立的檔案發生衝突,這點需要注意。
你也可以不要選擇Manual/None,直接使用系統建立好的NSManagedObject,同樣會有4個檔案,只是在工程中是看不到的,使用的時候直接匯入#import "Person+CoreDataClass.h"標頭檔案就可以了。
手動建立NSManagedObject
手動創建出來的是這樣4個檔案
10.png
還要注意程式語言的選擇,Swift或OC
程式語言
2.Core Data堆疊的介紹與使用
下面要做的就是對Core Data進行初始化,實現本地資料的儲存。
需要用到的類有三個:
NSManagedObjectModel 資料模型的結構資訊
NSPersistentStoreCoordinator 資料持久層和物件模型協調器
NSManagedObjectContext 物件的上下文managedObject 模型
如下圖所示,一個context內可以有多個模型物件,不過在大多數的操作中只存在一個context,並且所有的物件存在於那個context中。
物件和他們的context是相關聯的,每個被管理的物件都知道自己屬於哪個context,每個context都知道自己管理著哪些物件。
Core Data從系統讀或寫的時候,有一個持久化儲存協調器(persistent store coordinator),並且這個協調器在檔案系統中與SQLite資料庫互動,也連線著存放模型的上下文Context。
Core Data
下圖是比較常用的方式:
Core Data堆疊
下面我們來一步步實現Core Data堆疊的建立。
- 首先在AppDelegate中定義一個NSManagedObjectModel屬性。然後利用懶載入來建立NSManagedObjectModel物件。並且要注意建立時候的字尾用momd,程式碼如下:
//建立屬性
@property (nonatomic, readwrite, strong) NSManagedObjectModel *managedObjectModel;
//懶載入
- (NSManagedObjectModel *)managedObjectModel {
if (!_managedObjectModel) {
//注意副檔名為 momd
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataTest" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
return _managedObjectModel;
}
- 建立協調器NSPersistentStoreCoordinator,同樣的先在AppDelegate中來一個屬性,然後懶載入。
//屬性
@property (nonatomic, readwrite, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
//懶載入
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (!_persistentStoreCoordinator) {
//傳入之前建立好了的Model
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
//指定sqlite資料庫檔案
NSURL *sqliteURL = [[self documentDirectoryURL] URLByAppendingPathComponent:@"CoreDataTest.sqlite"];
//這個options是為了進行資料遷移用的,有興趣的可以去研究一下。
NSDictionary *[email protected]{NSMigratePersistentStoresAutomaticallyOption:@(YES),NSInferMappingModelAutomaticallyOption:@(YES)};
NSError *error;
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:sqliteURL
options:options
error:&error];
if (error) {
NSLog(@"建立協調器失敗: %@", error.localizedDescription);
}
}
return _persistentStoreCoordinator;
}
//獲取document目錄
- (nullable NSURL *)documentDirectoryURL {
return [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
}
- 建立NSManagedObjectContext,同樣的是屬性+懶載入。
//屬性
@property (nonatomic, readwrite, strong) NSManagedObjectContext *context;
//懶載入
- (NSManagedObjectContext *)context {
if (!_context) {
_context = [[NSManagedObjectContext alloc ] initWithConcurrencyType:NSMainQueueConcurrencyType];
// 指定協調器
_context.persistentStoreCoordinator = self.persistentStoreCoordinator;
}
return _context;
}
3.運用Core Data對資料進行增刪改查
- 新增資料
使用NSEntityDesctiption類的一個方法建立NSManagedObject物件。引數一是實體類的名字,引數二是之前建立的Context。
為物件賦值,然後儲存。
@implementation ViewController {
NSManagedObjectContext *context;
}
- (void)viewDidLoad {
[super viewDidLoad];
//拿到managedObjectContext
context = [AppDelegate new].context;
//運用NSEntityDescription建立NSManagedObject物件
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
//為物件賦值
person.name = @"Jack";
person.age = 17;
NSError *error;
//儲存到資料庫
[context save:&error];
}
- 查詢資料
Core Data從資料庫中查詢資料,會用到三個類:
NSFetchRequest:一條查詢請求,相當於 SQL 中的select語句
NSPredicate:謂詞,指定一些查詢條件,相當於 SQL 中的where
NSSortDescriptor:指定排序規則,相當於 SQL 中的 order by
NSFetchRequest中有兩個屬性:
predicate:是NSPredicate物件
sortDescriptors:它是一個NSSortDescriptor陣列,陣列中前面的優先順序比後面高。可以有多個排列規則。
//更多NSFetchRequest屬性
fetchLimit:結果集最大數,相當於 SQL 中的limit
fetchOffset:查詢的偏移量,預設為0
fetchBatchSize:分批處理查詢的大小,查詢分批返回結果集
entityName/entity:資料表名,相當於 SQL中的from
propertiesToGroupBy:分組規則,相當於 SQL 中的group by
propertiesToFetch:定義要查詢的欄位,預設查詢全部欄位
設定好NSFetchRequest之後,呼叫NSManagedObjectContext的executeFetchRequest方法,就會返回結果集了。
//Xcode自動建立的NSManagedObject會生成fetchRequest方法,可以直接得到NSFetchRequest
NSFetchRequest *fetchRequest = [Person fetchRequest];
//也可以這樣獲得:[NSFetchRequest fetchRequestWithEntityName:@"Student"];
//謂詞
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"age == %@", @(16)];
//排序
NSArray<NSSortDescriptor *> *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]];
fetchRequest.sortDescriptors = sortDescriptors;
//運用executeFetchRequest方法得到結果集
NSArray<Person *> *personResult = [context executeFetchRequest:fetchRequest error:nil];
Xcode自己有一個NSFetchRequest的code snippet,“fetch”,出現的結果如下圖。
snippet
NSFetchRequest的code snippet: “fetch”
- 更新資料
更新資料比較簡單,查詢出來需要修改的資料之後,直接修改值,然後用context save就可以了。
for (Person *person in personResult) {
//直接修改
person.age = 26;
}
//別忘了save一下
[context save:&error];
- 刪除資料
查詢出來需要刪除的資料之後,呼叫 NSManagedObjectContext 的deleteObject方法就可以了。
for (Person *person in personResult) {
//刪除資料
[context deleteObject:person];
}
//別忘了save
[context save:&error];
至此Core Data的基礎內容就講完了。
後記:
iOS中有多種資料持久化的方法,要根據具體情景選用最合適的技術。
推薦簡單又好用的分類集合:WHKit
github地址:https://github.com/remember17
作者:remember17
連結:https://www.jianshu.com/p/e88880be794f
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。