iOS 数据库

作者: CowboyBebop | 来源:发表于2019-02-13 16:23 被阅读0次

    一,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];
    

    相关文章

      网友评论

        本文标题:iOS 数据库

        本文链接:https://www.haomeiwen.com/subject/hitajqtx.html