iOS 數據庫

一,FMDB

  • 1,FMDB 的基本使用
    創建數據庫
FMDatabase * db = [FMDatabase databaseWithPath:dbPath];
if ([db open]) {
        NSLog(@"數據庫打開了");
}else{
        NSLog(@"數據庫打開失敗");
}
return db;

創建表單

NSFileManager * fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: self.dbPath]) {
        FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath];
        if ([db open]) {
            NSString * sql = @"CREATE TABLE 'User' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'name' VARCHAR(30),'password' VARCHAR(30))";
            BOOL res = [db executeUpdate:sql];
            if (res) {
                NSLog(@"create table success");

            }else{
                NSLog(@"create table error");
            }
            [db close];
        } else{
            NSLog(@"open db error");
        }
    }

static int idx = 1;
FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath];
if ([db open]) {
        NSString * sql = @"insert into user (name,password) values(?, ?)";
        NSString * name = [NSString stringWithFormat:@"tangqiao%d",idx++];
        BOOL res = [db executeUpdate:sql, name ,@"boy"];
        if (res) {
            NSLog(@"insert data success");
        }else
        {
            NSLog(@"insert data error");
        }
    }

FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath];
    if ([db open]) {
        NSString * sql = @"delete from user";
        BOOL res = [db executeUpdate:sql];
        if (res) {
            NSLog(@"delete db success");
        }else{
            NSLog(@"delete db error");
        }
        [db close];
    }

FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath];
if ([db open]) {
        NSString * sql = @"update user set name = ? where id = ?";
        BOOL res = [db executeUpdate:sql,@"新的名字",@(483)];
        if (res) {
            NSLog(@"uodate db success");
        }else{
            NSLog(@"uodate db error");
        }
}

FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath];
    if ([db open]) {
        NSString * sql = @"select * from user";
        FMResultSet * rs = [db executeQuery:sql];
        while ([rs next]) {
            int userId = [rs intForColumn:@"id"];
            NSString * name = [rs stringForColumn:@"name"];
            NSString * pass = [rs stringForColumn:@"password"];
            NSLog(@"userid = %d,name = %@,password = %@",userId,name,pass);
        }
        [db close];
    }

FMDatabase這個類, 他不是線程安全的, 如果在多個線程中同時使用一個FMDataBase對象來存取數據的話, 有可能會發生數據錯亂,因此為了保證線程安全, 要使用FMDatabaseQueue這個類來操作數據。

FMDatabaseQueue * queue = [FMDatabaseQueue databaseQueueWithPath:self.dbPath];
dispatch_queue_t q1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(q1, ^{
        for (int i = 0; i < 100; i++) {
            [queue inDatabase:^(FMDatabase * _Nonnull db) {
                NSString * sql = @"insert into user (name,password) values(?,?)";
                NSString * name = [NSString stringWithFormat:@"queue11 %d",I];
                BOOL res = [db executeUpdate:sql,name,@"boy"];
                NSLog(@"currentThread = %@",[NSThread currentThread]);
                if (res) {
                    NSLog(@"q1 add data success: %@",name);
                }else{
                    NSLog(@"q1 add data error: %@",name);
                }
            }];
        }
    });
dispatch_async(q2, ^{
        for (int i = 0; i < 100; i++) {
            [queue inDatabase:^(FMDatabase * _Nonnull db) {
                NSString * sql = @"insert into user (name,password) values(?,?)";
                NSString * name = [NSString stringWithFormat:@"queue22 %d",I];
                BOOL res = [db executeUpdate:sql,name,@"boy"];
                NSLog(@"currentThread = %@",[NSThread currentThread]);
                if (res) {
                    NSLog(@"q2 add data success: %@",name);
                }else{
                    NSLog(@"q2 add data error: %@",name);
                }
            }];
        }
});
  • 2,FMDB的二次封裝
    問題:
    1, 直接存取模型,模型中包含數組,字典,其他模型屬性,這時候改怎么處理,

在遇到其他類型或者自定義類型時,全部轉化成字符串,模型轉字符串,字典和數組轉字符串,下面這些第三方庫已經幫我們做了這些處理。
BGFMDB
LKDBHelper-SQLite-ORM
JRDB

2,模型增加字段,表增加字段了,這時候該怎么處理

先從表中取出所有字段,然后和模型的所有成員變量比較,如果有不同的,表示模型增加字段了,這時候就插入字段。

二,WCDB

WCDB 是微信團隊開源的一款高效、完整、易用的移動數據庫框架,優點:1,開發者無須為了拼接SQL的字符串而寫一大坨膠水代碼,2,WCDB支持多線程讀與讀、讀與寫并發執行,寫與寫串行執行。缺點:需使用Objective-C++,導入 <WCDB/WCDB.h> 的類必須改成.mm 類型。

官方文檔:iOS macOS使用教程

  • 1,WCDB 的基本使用
    WCDB會在第一次訪問數據庫時,自動打開數據庫,不需要開發者主動操作。
  • 2,WCDB 的封裝,隔離c++ 代碼
    項目引入WCDB 的類都需要改成.mm ,這樣有點不太好,所以我創建了管理類,簡單的封裝增刪改查的基本操作,但是像 where 和 limit 這樣的c++ 類型不太好封裝,所以還是沒有充分使用WCDB 的全部功能。具體案例請看wcdbDemo。
  • 3, WCTTableCoding文件模版
    通過這個模板,我們在創建模型的時候,會自動為我們創建WCTTableCoding的category。
    已獲取 WCDB 的 Github 倉庫的開發者,可以手動運行 cd path-to-your-wcdb-dir/tools/templates; sh install.sh;

安裝完成之后:


image.png
image.png
  • 4, 模型增加字段, 表格字段不同
  • 5, 模型當中嵌套模型

三,CoreData

CoreData - 基礎使用
MagicalRecord使用

  • 1,基本使用

1,新建項目,創建后綴為.xcdatamodeld的模型文件

image.png

2,添加實體
點擊列表下方的Add Entity按鈕,會彈出Add Entity、Add Fetch Request、Add Configuration選項,可以添加實體、請求模板、配置信息。這里先選擇Add Entity來添加一個實體,命名為Person。
添加Person實體后,會發現一個實體對應著三部分內容,Attributes、Relationships、Fetched Properties,分別對應著屬性、關聯關系、獲取操作。

3,創建托管對象類文件
點擊后綴名為.xcdatamodeld的模型文件,選擇Xcode的Editor -> Create NSManagedObject Subclass -> 選擇模型文件 -> 選擇實體,生成Department實體對應的托管對象類文件。


image.png

切換語言
點擊后綴名為.xcdatamodeld的模型文件,最右側有個language 的選項,切換下。


image.png

到這里創建文件就算完成了。

4,CoreData增刪改查
創建NSManagedObjectContext
NSManagedObjectContext初始化方法的枚舉值參數主要有三個類型:
NSConfinementConcurrencyType 如果使用init方法初始化上下文,默認就是這個并發類型。在iOS9之后已經被蘋果廢棄,不建議用這個API,調用某些比較新的CoreData的API可能會導致崩潰。
NSPrivateQueueConcurrencyType 私有并發隊列類型,操作都是在子線程中完成的。
NSMainQueueConcurrencyType 主并發隊列類型,如果涉及到UI相關的操作,應該考慮使用這個參數初始化上下文。

- (NSManagedObjectContext * )contextWithModel:(NSString *)modelName;
{
    NSManagedObjectContext * context= [[NSManagedObjectContext alloc] initWithConcurrencyType:(NSMainQueueConcurrencyType)];
    NSURL * url = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
    NSManagedObjectModel * model = [[NSManagedObjectModel alloc] initWithContentsOfURL:url];
    NSPersistentStoreCoordinator * coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    NSString * path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    path = [path stringByAppendingFormat:@"/%@.sqlite", modelName];
    NSLog(@"path = %@",path);
/*
 NSMigratePersistentStoresAutomaticallyOption設置為YES,CoreData會試著把低版本的持久化存儲區遷移到最新版本的模型文件。
 NSInferMappingModelAutomaticallyOption設置為YES,CoreData會試著以最為合理地方式自動推斷出源模型文件的實體中,某個屬性到底對應于目標模型文件實體中的哪一個屬性
*/
    NSDictionary * options = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
                               NSInferMappingModelAutomaticallyOption : @YES
                               };
//    options : 用于設置數據庫增刪字段和數據庫升級
    [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:path] options:options error:nil];
    
    context.persistentStoreCoordinator = coordinator;
    return context;
}

持久化存儲調度器(NSPersistentStoreCoordinator)

NSPersistentStoreCoordinator有四種可選的持久化存儲方案,用得最多的是SQLite的方式。其中Binary和XML這兩種方式,在進行數據操作時,需要將整個文件加載到內存中,這樣對內存的消耗是很大的。
NSSQLiteStoreType : SQLite數據庫
NSXMLStoreType : XML文件
NSBinaryStoreType : 二進制文件
NSInMemoryStoreType : 直接存儲在內存中

    static int i = 1;
    Employee * emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:self.modelMOC];
    emp.name = [NSString stringWithFormat:@"zdq_%@",@(i)];
    emp.userId = [NSString stringWithFormat:@"%@",@(i)];
    emp.height = 1.30 + i / 10.0;
    NSLog(@"height = %@",@(emp.height));
    NSError * error = nil;
    if (self.modelMOC.hasChanges) {
        [self.modelMOC save:&error];
    }
    if (error) {
        NSLog(@"insert error = %@",error);
    }
    i ++;

    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"userId = %@",@"2"];
    request.predicate = predicate;
    NSError * error = nil;
    NSArray * arr = [self.modelMOC executeFetchRequest:request error:&error];
    
    [arr enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [self.modelMOC deleteObject:obj];
    }];
    if ([self.modelMOC hasChanges]) {
        [self.modelMOC save:nil];
    }
    if (error) {
        NSLog(@"delete error = %@",error);
    }

    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"userId = %@",@"1"];
    request.predicate = predicate;
    NSError * error = nil;
    NSArray * arr = [self.modelMOC executeFetchRequest:request error:&error];
    [arr enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        obj.name = @"更新了";
    }];
    if ([self.modelMOC hasChanges]) {
        [self.modelMOC save:nil];
    }
    if (error) {
        NSLog(@"update error = %@",error);
    }

NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
NSError * error = nil;
NSArray * arr = [self.modelMOC executeFetchRequest:request error:&error];
 [arr enumerateObjectsUsingBlock:^(Employee *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"name = %@,userId = %@",obj.name,obj.userId);
    }];
    if (error) {
        NSLog(@"query error = %@",error);
    }

查詢排序

    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    NSError * error = nil;
    NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:YES];
    request.sortDescriptors = @[sort];
    NSArray * arr = [self.modelMOC executeFetchRequest:request error:&error];
    [arr enumerateObjectsUsingBlock:^(Employee *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"name = %@,userId = %@,height = %@",obj.name,obj.userId,@(obj.height));
    }];

分頁查詢

 NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    request.fetchOffset = 1;
    request.fetchLimit = 3;
//    NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:@"userId" ascending:YES];
//    request.sortDescriptors = @[sort];
    NSError * error = nil;
    NSArray * arr = [self.modelMOC executeFetchRequest:request error:&error];
    [arr enumerateObjectsUsingBlock:^(Employee *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"name = %@,userId = %@,height = %@",obj.name,obj.userId,@(obj.height));
    }];
    if (error) {
        NSLog(@"page query error = %@",error);
    }

批量更新

    NSBatchUpdateRequest * request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:@"Employee"];
    request.resultType = NSUpdatedObjectsCountResultType;
    request.propertiesToUpdate = @{@"height" : @(2.11)};
    NSError * error = nil;
    NSBatchUpdateResult * result = [self.modelMOC executeRequest:request error:&error];
    NSLog(@"update count %@",@([result.result integerValue]));
    if (error) {
        NSLog(@"batch update error = %@",error);
    }
    [self.modelMOC refreshAllObjects];

批量刪除

NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"height > %@",@(2.0)];
request.predicate = predicate;
NSBatchDeleteRequest * deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
deleteRequest.resultType = NSBatchDeleteResultTypeCount;
NSError * error = nil;
NSBatchDeleteResult * result = [self.modelMOC executeRequest:deleteRequest error:&error];
NSLog(@"update count %@",@([result.result integerValue]));
if (error) {
        NSLog(@"batch update error = %@",error);
}
[self.modelMOC refreshAllObjects];
  • 2,數據庫升級

在開發中經常會有模型文件增加字段的情況出現,如果新版本增加字段了,不考慮兼容之前版本,那么上線之后APP就會崩潰。

創建新版本模型文件
選中需要做遷移的模型文件 -> 點擊菜單欄Editor -> Add Model Version -> 選擇基于哪個版本的模型文件(一般都是選擇目前最新的版本),新建模型文件完成。


image.png

創建完成之后


image.png

可以勾選當前使用的模型文件


image.png

Mapping Model 遷移方案

  • 3,MagicalRecord框架使用
    國外開發者開源了一個基于CoreData封裝的第三方——MagicalRecord,就像是FMDB封裝SQLite一樣,MagicalRecord封裝的CoreData,使得原生的CoreData更加容易使用。并且MagicalRecord降低了CoreData的使用門檻,不用去手動管理之前的PSC、MOC等對象。

根據Github上MagicalRecord的官方文檔,MagicalRecord的優點主要有三條:

  1. 清理項目中CoreData代碼
  2. 支持清晰、簡單、一行式的查詢操作
  3. 當需要優化請求時,可以獲取NSFetchRequest進行修改

導入MagicalRecord

pod 'MagicalRecord', '2.3.2'

創建模型文件和之前的步驟一樣
基本設置,在APPdelegate 中 設置log 級別和數據庫名稱。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
//    設置log級別
    [MagicalRecord setLoggingLevel:(MagicalRecordLoggingLevelVerbose)];
//    設置數據庫名稱
    [MagicalRecord setupCoreDataStackWithStoreNamed:SQLiteName];
    return YES;
}

內存警告的時候,清除

- (void)applicationWillTerminate:(UIApplication *)application {
//    清除
    [MagicalRecord cleanUp];
}

NSManagedObjectContext * context = [NSManagedObjectContext MR_defaultContext];
Person * p = [Person MR_createEntityInContext:context];
static int i = 1;
p.name = [NSString stringWithFormat:@"zdq%@",@(i)];
p.age = i + 10;
I++;
[context MR_saveToPersistentStoreAndWait];

    NSArray * arr = [Person MR_findByAttribute:@"age" withValue:[NSNumber numberWithInt:12]];
    [arr enumerateObjectsUsingBlock:^(Person *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [obj MR_deleteEntity];
    }];
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];

    NSArray * arr = [Person MR_findByAttribute:@"name" withValue:@"zdq2"];
    [arr enumerateObjectsUsingBlock:^(Person *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        obj.name = [NSString stringWithFormat:@"更新了"];
    }];
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];

    NSArray * arr = [Person MR_findAllSortedBy:@"age" ascending:YES];
    [arr enumerateObjectsUsingBlock:^(Person *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"name = %@,age = %@",obj.name,@(obj.age));
    }];

四,Relam

官方文檔
Realm在iOS中的簡單使用
Realm數據庫 從入門到“放棄”

  • 1、Realm簡介

Realm是由美國YCombinator孵化的創業團隊歷時幾年打造,第一個專門針對移動平臺設計的數據庫
Realm是一個跨平臺的移動數據庫引擎,目前支持iOSAndroid平臺,同時支持Objective-CSwiftJavaReact NativeXamarin等多種編程語言
Realm并不是對SQLite或者CoreData的簡單封裝, 是由核心數據引擎C++打造,是擁有獨立的數據庫存儲引擎,可以方便、高效的完成數據庫的各種操作

  • 2、Realm的優勢與亮點

開源。Realm移動端數據庫相關代碼已全部開源。數千開發者在GitHub上參與了相關工作。另外還有幾百個Realm數據庫相關的擴展。
簡單易用:Core DataSQLite龐大的學習量和繁雜的代碼足以嚇退絕大多數剛入門的開發者,而換用Realm,則可以極大地減少學習代價和學習時間,讓應用及早用上數據存儲功能
跨平臺:現在絕大多數的應用開發并不僅僅只在iOS平臺上進行開發,還要兼顧到Android平臺的開發。為兩個平臺設計不同的數據庫是不明智的,而使用Realm數據庫,iOSAndroid無需考慮內部數據的架構,調用Realm提供的API就可以完成數據的交換
線程安全。程序員無需對在不同線程中,對數據庫的讀取一致性做任何考慮,Realm會保證每次讀取都得到一致的數據

  • 3,可視化工具Realm Browser

為了配合Realm的使用,Realm還提供了一個輕量級的數據庫查看工具Realm Browser,借助這個工具,開發者可以查看數據庫當中的內容,并執行簡單的插入和刪除操作

  • 4, 導入

可以通過pod 導入,

  pod 'Realm'
  • 5,插件

Realm提供了一個Xcode插件,來方便的創建RLMObject類,這需要我們首先安裝相關的插件,打開Realm文件夾中的plugin/RealmPlugin.xcodeproj并進行編譯,重啟Xcode之后插件即可生效

  • 6, 創建模型
    運行插件之后就會出來這個選項


    image.png

    所有的模型必須繼承 RLMObject


    image.png

    static int i = 1;
    Student * student = [[Student alloc] initWithValue:@{@"age": @(i),@"name":[NSString stringWithFormat:@"zdq%@",@(i)],@"sex":@(YES)}];
//    第一種
//    RLMRealm * realm = [RLMRealm defaultRealm];
//    [realm beginWriteTransaction];
//    [realm addObject:student];
//    [realm commitWriteTransaction];
//    第二種
    RLMRealm * realm = [RLMRealm defaultRealm];
    [realm transactionWithBlock:^{
        [realm addObject:student];
    }];
    i++;

    RLMRealm * realm = [RLMRealm defaultRealm];
    RLMResults * resArr = [Student objectsInRealm:realm where:@"name = 'zdq1'"];
    Student * stu = resArr.firstObject;
    [realm transactionWithBlock:^{
        [realm deleteObject: stu];
    }];

RLMRealm * realm = [RLMRealm defaultRealm];
RLMResults * resArr = [Student objectsWhere:@"age == 2"];
for (NSInteger i = 0; i < resArr.count; i++) {
        Student * obj = [resArr objectAtIndex:i];
        [realm transactionWithBlock:^{
            obj.name = @"更新了";
        }];
}

RLMResults * resArr = [Student allObjects];
NSLog(@"resArr = %@",resArr);

條件查詢

RLMResults * resArr = [Student objectsWhere:@"age > 3"];
    NSLog(@"resArr = %@",resArr);
//    排序
    RLMResults * stuArr = [resArr sortedResultsUsingKeyPath:@"age" ascending:YES];
    NSLog(@"stuArr = %@",stuArr);

數據庫遷移

RLMRealmConfiguration * con = [RLMRealmConfiguration defaultConfiguration];
con.fileURL = [[[con.fileURL URLByDeletingLastPathComponent]
URLByAppendingPathComponent:userName] URLByAppendingPathExtension:@"realm"];
int newVersion = 1;
con.schemaVersion = newVersion;
[con setMigrationBlock:^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
if (oldSchemaVersion < newVersion) {
            NSLog(@"數據結構會自動遷移");
        }
}];
[RLMRealmConfiguration setDefaultConfiguration:con];
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容