当前位置: 首页 > 科技观察

管理iOS本地持久化存储(专注于数据库SQLite)

时间:2023-03-17 21:14:42 科技观察

公司项目已经存在两年了,版本已经到了三点,但是本地持久化数据存储一直使用的是GVUserDefaults,它扩展了NSUserDefaults第三方库.但是随着业务的发展,需要存储的地方越来越多,GVUserDefaults越来越不能满足需求。当我们受不了的时候,经过一番讨论,我们决定使用FMDB这个封装了SQLite3的第三方库。本文以此为主线,梳理了一些关于数据库和本地化存储的知识,但是这篇文章完全没有提到CoreData。喜欢用CoreData的同学,让我说声抱歉,浪费了你们宝贵的20多岁的时间。这篇文章的逻辑如下图所示:iOS本地化持久化存储方式概述说到iOS本地化存储方式,大家可能并不陌生。NSUserDefault、File、Keychain、DataBase无非就是这几个方法。NSUserDefault、File:这两个使用方法很简单。需要注意的一点是,存储的对象需要遵守和实现NSCoding协议中的两个方法。适用范围也是一些小规模的数据。实际上,NSUserDefault的底层实现仍然保存在一个.plist文件中。Keychain:用于存储一些隐私信息,如密码、证书等,Keychain中存储的信息不会在App删除时丢失,并且在用户重新安装App后仍然有效。这同样适用于应用程序之间的数据共享。DataBase:说到存储,就不得不提到DataBase技术。移动端使用的DBMS一般是SQLite3。iOS下,SQLite3的API是纯C语言。这样一来,我们长期从事面向对象开发的朋友们突然找不到对象了,我们有点慌了。幸运的是,开源社区已经出现了FMDB这样优秀的第三方框架来帮助我们检索对象。同样,Apple也发布了一项名为CoreData的新技术(不过CoreData不是本文的重点)。数据库适合存储大规模的数据,而且比上面的方法更方便查找,所以在存储大量数据的时候,还是需要用到数据库的,这也是我们放弃GVUserDefaults的原因。数据库基础知识数据库首先要有数据Data,然后数据的名称升级为DataBase。这时候就需要一个管理系统来管理数据库,即DataBaseManagerSystem。最后,它成为一个具有DataBaseAdministrator和上述名称的系统。即所谓的DataBaseSystem。Data-->DataBase-->DataBaseManagerSystem-->DataBaseSystemData:数据库存储的基本对象。DataBase:说到数据库,大家有个模糊的概念,不就是一个用来存放数据的大仓库吗?没什么可说的。其实这样理解已经很好了,但是更专业一点的是,数据库就是有组织地存储的数据的集合。DataBaseManagerSystem:数据库管理系统是介于用户和操作系统之间的一层数据管理软件。常见的DBMS包括MySQL、PostgreSQL、MicrosoftSQLServer、Oracle、Sybase和IBMDB2。当然,这些都是服务器端使用的DBMS,而移动端使用的DBMS是SQLite3,这也是本文的重点。DataBaseSystem:将数据库引入计算机系统后的系统,一般由数据库、数据库管理系统、应用系统、数据库管理员(DBA)组成。通常情况下,数据库系统被称为数据库,所以有时我们会感到困惑。当你说DBS时,他会想到DB。等你转成DB,他又开始说DBS了。所以,当你真正想要了解一些知识的时候,基本的概念一定要比较清楚,才不容易搞混。SQL语句SQL(StructuredQueryLanguage),结构化查询语言,一种专门用来与数据库进行通信的语言。既然要操作数据库,就必须会写SQL语句。下面我简单列出几条简单的SQL语句,仅供参考。如果想深入了解SQL语句,请参考其他资料,本文在此不做过多介绍。这里以student表为例写几条简单的SQL语句//创建一张表(table)一个student表表名:student字段id:学号,作为主键,type为integer字段名:学生姓名,类型为字符串,不能为空字段age:学生年龄,类型为整数,可以为空。字段也叫列(column)createtableifnotexistsstudent(idintegerprimarykeyautoincrement,nametextnotnull,ageinteger);//删除学生表droptableifexistsstudent;//插入一条记录,主键id会自动增加并自动赋值,这条记录也叫row(row)inserttostudent(name,age)values('Xiaoming',20);//删除名字为小明的学生,其中关键字where是条件,如果没有这个条件,会影响整个表deletefromstudentwherename='Xiaoming';//更新符合条件的记录updatestudentsetage=21wherename='Xiaoming';//查询符合条件的记录select*fromstudentwherename='Xiaoming';数据库使用如果不用CoreData,那么无论是使用纯C语言的SQLite3库,还是使用封装了它的FMDB,都得设计适合自己业务的表结构。关于表的设计,可以有两种设计模式。数据库表的设计第一种设计方式是将模型中的每个属性作为表的一个字段,这样查询和读取更方便,但是个人感觉这种模式适合的业务是记录数不多。而且字段尽量不要改。创建表后,修改表结构还是比较麻烦的。具体使用请参考以下代码:@interfaceStudent:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,assign)NSUIntegerage;@end//数据存放路径NSString*document=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];NSString*dbpath=[documentstringByAppendingPathComponent:@"toyun.sqlite"];//链接数据库self.db=[FMDatabasedatabaseWithPath:dbpath];//打开数据库if([self.dbopen]){[self.dbexecuteUpdate:@"createtableifnotexistsstudent(idintegerprimarykeyautoincrement,nametextnotnull,ageintegernotnull)"];}//实例化对象,即要存储的对象NSMutableArray*array=[NSMutableArray数组];for(NSIntegeri=0;i<10;i++){Student*student=[[Studentalloc]init];student.name=[NSStringstringWithFormat:@"小明%ld",i];student.age=arc4random()%20+10;[数组添加对象:学生];}//AddInsertthedatabaseintothetablefor(Student*studentinarray){[self.dbexecuteUpdateWithFormat:@"insertintostudent(name,age)values(%@,%ld)",student.name,student.age];}[自我.db关闭];NSLog(@"dbpath===%@",dbpath);第二种设计方式这种设计方式是将模型作为一个字段,直接将模型转成NSData存储在这个字段中。这里需要指出的是,最重要的是模型必须实现NSCoding协议,但是在实际项目中,我们的字典到模型的转换大多使用第三方库,现在比较流行的三个第三方库字典到模型的转换、Mantle、MJExtension和YYModel,都默认处理这个问题。所以这里稍微注意一下。第二种方式更适合大量记录,对业务关联性不太敏感。而如果有查找排序的需求,可以从模型中挑出一些属性作为附表的一些字段。工具使用,参考下面的代码:@interfaceStudent:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,assign)NSUIntegerage;@end@implementationStudent-(void)encodeWithCoder:(NSCoder*)aCoder{[aCoderencodeObject:_nameforKey:@"name"];[aCoderencodeInteger:_ageforKey:@"age"];}-(nullableinstancetype)initWithCoder:(NSCoder*)aDecoder{if(self=[superinit]){_name=[aDecoderdecodeObjectForKey:@"name"];_age=[aDecoderdecodeIntegerForKey:@"age"];返回自我;}@endNSString*document=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];NSString*dbpath=[documentstringByAppendingPathComponent:@"yeemiao.sqlite"];self.db=[FMDatabasedatabaseWithPath:dbpath];if([self.dbopen]){[self.dbexecuteUpdate:@"createtableifnotexistsstudent(idintegerprimarykeyautoincrement,modelblob)"];}NSMutableArray*array=[NSMutableArrayarray];对于(NSIntegeri=0;我<10;我++){学生*学生=[[Studentalloc]init];student.name=[NSStringstringWithFormat:@"小明%ld",i];student.age=arc4random()%20+10;[数组添加对象:学生];}//转换成NSData存入数据库for(Student*studentinarray){NSData*data=[NSKeyedArchiverarchivedDataWithRootObject:student];[self.dbexecuteUpdateWithFormat:@"insertintostudent(model)values(%@)",data];}//从数据库中查询数据FMResultSet*set=[self.dbexecuteQuery:@"select*fromstudent"];NSMutableArray*resultArray=[NSMutableArrayarray];while(set.next){NSData*data=[setobjectForColumnName:@"model"];学生*学生=[NSKeyedUnarchiverunarchiveObjectWithData:data];[结果数组添加对象:学生];}[self.dbclose];数据库使用中的线程安全要在多线程中操作数据库,必须要考虑线程安全问题,否则可能会造成数据乱序的问题,解决方法有很多。可以自己加锁,但是读写速度要求高的不建议加锁。SQLite3直接支持多线程,SQLite3库提供了单线程、多线程、序列化三种方法。同样的,FMDB本身也提供了类FMDatabaseQueue用于数据库的多线程操作,使用起来比较简单。删除缓存现在大部分app都有本地化存储,但是并不是每个app都有删除缓存的功能,至少我们自己的app没有删除缓存的功能,缓存是为了节省流量,删除缓存的目的是为了节省空间.我觉得这两个功能同等重要,但是这个需求已经提了好几次了,产品也不太重视!尤其是在16G版本,这个功能还能提升一些用户体验。这个功能实现起来并不难。弄清楚各种存储方式后,他们将文件存储在该文件夹下。我们存放不同数据的地方明显不同,然后根据需要删除相应的数据。OK,沙箱具体目录如下://沙箱下的文件夹目录Documents:iTunes会同步这个文件夹,LibraryCaches:缓存文件夹不要删除,删除主要是从缓存中删除的文件夹Preferences:NSUserDefault是写入这个文件夹,iTunes会同步,tmp不要删除:系统创建的临时文件夹随时可能被删除。在执行清除缓存功能时,可以删除Library/Caches文件夹下的所有文件,也可以根据业务需要删除指定文件。文件夹。综上所述,本文还是以原理为主,具体使用不多说。具体用途多种多样,但基本原理比较统一。关于数据库CURD,由于我们不是做服务端开发的,所以没有深究。如果你想研究它,你可以查看其他资料。除了上述之外,具体使用中还可以考虑数据库版本迁移和数据库同步的需求。这些受限于我的水平,写这篇文章的时候也没有提到可以参考参考博文。iOS应用架构讲本地持久化方案和动态部署。NSUserDefault和File的使用方法很简单,就不做介绍了。CoreData太重量级了,我也没有实际在项目中使用过,这里就不介绍了。(版权归作者所有,转载请联系作者授权,本文已获得作者授权转载)