公司的项目存在已有两年,版本也到三点几了,可是本地持久化数据存储,始终用的是GVUserDefaults
这个对NSUserDefaults
进行了扩展的第三方库。但随着业务的发展,须要存储的地方愈来愈多,GVUserDefaults
也越来也不能适应需求,当咱们都忍受不了的时候,通过一番商讨以后,决定使用FMDB
这个封装了SQLite3
的第三方库。此篇文章以此为主线,理一理数据库和本地化储存的一些知识,可是此篇文章丝毫没有提到CoreData
,喜欢使用CoreData
的同窗,这里先说声对不起了浪费了您20s宝贵的时间。
html
此篇文章的逻辑以下图所示:ios
图0-0 此篇文章的逻辑图sql
提及iOS本地化储存的方式,你们估计在也熟悉不过了,NSUserDefault
、File
,Keychain
、DataBase
无非也就这几种方式。数据库
NSUserDefault
、File
:这两种使用方式都很简单,须要注意的一点就是所存储的对象都须要遵照并实现NSCoding
协议中的两个方法,适用的范围也都是一些小规模数据,其实NSUserDefault
的底层实现仍是以.plist
文件进行储存的。设计模式
Keychain
:用于储存一些私密信息,好比密码、证书等等,Keychain里保存的信息不会因App被删除而丢失,在用户从新安装App后依然有效。一样也适用于应用之间数据共享。缓存
DataBase
:说到储存,就不能不提DataBase技术
,移动端所用的DBMS
通常都是SQLite3
,在iOS下,SQLite3
的API
是纯C语言
的,这样咱们一直以来面向对象开发的朋友们,忽然找不到了对象,有点那么的惶恐不安。好在开源社区出现了像FMDB
这样优秀的第三方框架帮咱们找回了对象,一样苹果本身也出了新的技术就是所谓的CoreData
(但CoreData
并非此篇文章介绍的重点)。数据库适合储存大规模数据,并且查找起来也比上述方式方便,因此大量储存的时候,仍是要动用数据库,这也是咱们放弃GVUserDefaults
的缘由。安全
首先要有数据Data
,而后数据多了名字就升级了称为数据库DataBase
,这个时候就须要有管理系统去管理数据库也就是DataBaseManagerSystem
,最后佐以DataBaseAdministrator
及上述名称成为了一个系统就是所谓的DataBaseSystem
。多线程
Data --> DataBase --> DataBaseManagerSystem --> DataBaseSystem架构
Data
: 数据库存储的基本对象。框架
DataBase
: 提及来数据库,你们都有一个模模糊糊的概念,不就是一个存放数据的大仓库吗,这还有什么好说的。其实这样理解就已经很好了,可是说的更专业一点就是数据库是一个以某种有组织的方式存储的数据集合。
DataBaseManagerSystem
: 数据库管理系统是位于用户与操做系统之间的一层数据管理软件。常见的DBMS
有MySQL
、PostgreSQL
、 Microsoft SQL Server
、Oracle
、Sybase
、IBM DB2
。固然这些都是用于服务端的DBMS
,移动端用的DBMS
是SQLite3
,这也是本文讲述的重点。
DataBaseSystem
: 在计算机系统中引入数据库后的系统,通常由数据库,数据库管理系统,应用系统,数据库管理成员(DBA)
构成。通常状况下称数据库系统为数据库,因此有时候咱们就会感到迷糊,你说DBS
,他觉得DB
,你转到DB
上了,他又开始讲DBS
。因此真正想理解透一些知识的时候,基本概念必定要清晰一些,这样才不容易迷糊。
SQL(Structured Query Language),结构化查询语言,专门用来与数据库通讯的语言。既然要操做数据库,SQL语句是必需要会写的。下面我简单列举几条简单的SQL语句仅供参考。想要学习更多的SQL语句的知识,请查阅其余资料,本篇在这儿不过多叙述。
// 创表(table)一张学生表 表名:student 字段id: 学生编号,做为主键,类型为整形 字段name:学生名字,类型为字符串,而且不能为空值 字段age: 学生年龄,类型为整形可为空。 其中字段又称之为列(column)create table if not exists student ( id integer primary key autoincrement, name text not null, age integer);// 删除学生表drop table if exists student;// 插入一条记录,主键id会自增加自动赋值, 其中记录又称之为行(row)insert into student (name, age) values ('小明', 20);// 删除名字为小明的学生, 这里的关键字where就是条件,若是无此条件,则影响整张表delete from student where name = '小明';// 更新符合条件的记录update student set age = 21 where name = '小明';// 查询符合条件的记录select * from student where name = '小明';
若是你没有使用CoreData
,那么不管你使用的是纯C语言
的SQLite3库
,仍是使用对其进行封装了的FMDB
,你都要设计出适合本身业务的表结构。关于表的设计,能够有两种设计模式。
以模型中的每一个属性做为表的一个字段,这样在查询、读取放面操做起来更为方便,可是我我的感受这种模式适合的业务是记录条数很少。并且字段尽量的不要更改。建立好表以后,在去修改表结构仍是听麻烦的一件事的。具体使用,可参考下面的代码:
@interface Student : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, assign) NSUInteger age;@end// 数据储存的路径NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];NSString *dbpath = [document stringByAppendingPathComponent:@"toyun.sqlite"];// 连接数据库self.db = [FMDatabase databaseWithPath:dbpath];// 打开数据库if ([self.db open]) { [self.db executeUpdate:@"create table if not exists student ( id integer primary key autoincrement, name text not null, age integer not null)"]; }// 实例化对象,即要储存的对象NSMutableArray *array = [NSMutableArray array];for (NSInteger i = 0; i < 10; i++) { Student *student = [[Student alloc] init]; student.name = [NSString stringWithFormat:@"小明%ld", i]; student.age = arc4random() % 20 + 10; [array addObject:student]; }// 将数据库插入表中for (Student *student in array) { [self.db executeUpdateWithFormat:@"insert into student (name, age) values (%@, %ld)", student.name, student.age]; } [self.db close];NSLog(@"dbpath === %@", dbpath);
这一种设计模式是将model
做为一个字段直接将model
转为NSData
储在此字段中,在这里要指出的是model
必须实现NSCoding协议,不过实际项目中咱们字典转模型大都是使用第三方库,而如今比较流行的三个字典转模型的第三方库Mantle
、MJExtension
、YYModel
都默认对此进行了处理,因此这儿稍微注意一下就好。第二种方法更适合于记录条数比较多,和业务的相关性不是太敏感,并且若是有查找排序这样的需求的话,能够从model
挑出某些属性做为表中附带的一些字段。具体使用,参考下面的代码:
@interface Student : NSObject <NSCoding>@property (nonatomic, copy) NSString *name;@property (nonatomic, assign) NSUInteger age;@end@implementation Student- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_name forKey:@"name"]; [aCoder encodeInteger:_age forKey:@"age"]; } - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { _name = [aDecoder decodeObjectForKey:@"name"]; _age = [aDecoder decodeIntegerForKey:@"age"]; } return self; }@endNSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];NSString *dbpath = [document stringByAppendingPathComponent:@"yeemiao.sqlite"];self.db = [FMDatabase databaseWithPath:dbpath];if ([self.db open]) { [self.db executeUpdate:@"create table if not exists student (id integer primary key autoincrement, model blob)"]; }NSMutableArray *array = [NSMutableArray array]; for (NSInteger i = 0; i < 10; i++) { Student *student = [[Student alloc] init]; student.name = [NSString stringWithFormat:@"小明%ld", i]; student.age = arc4random() % 20 + 10; [array addObject:student]; }// 转为NSData,存入数据库for (Student *student in array) { NSData *data = [NSKeyedArchiver archivedDataWithRootObject:student]; [self.db executeUpdateWithFormat:@"insert into student (model) values (%@)", data]; }// 从数据库中查数据FMResultSet *set = [self.db executeQuery:@"select * from student"];NSMutableArray *resultArray = [NSMutableArray array];while (set.next) { NSData *data = [set objectForColumnName:@"model"]; Student *student = [NSKeyedUnarchiver unarchiveObjectWithData:data]; [resultArray addObject:student]; } [self.db close];
在多线程中操做数据库,咱们就要考虑线程安全问题,否则就有可能引发数据错乱的问题。解决办法有不少,能够本身去加锁,可是读写速度要求高的话就不太建议加锁了。SQLite3
对于多线程是直接支持的,SQLite3库
提供了三种方式:single thread
、multi thread
、serialized
。一样 FMDB
本身也提供了多线程操做数据库的类FMDatabaseQueue
,这个使用起来仍是比较简单的。
如今大多数的App
都是有本地化储存的,可是却不是每一个App
都对应有删除缓存的功能,起码咱们本身的App
是没有作删除缓存这个功能的,缓存是为了节省流量,删除缓存是为了节省空间我认为这两个功能同等重要,可是这个需求提了几回,产品不怎么重视啊!特别是在16G的版本下,这个功能还能能提高一些用户体验的。
此功能到也不难实现,搞清楚各类储存方式他们把文件存储到了那个文件夹下面,不一样的数据咱们储存的地方明显也是不一样的,而后根据需求删除对应的数据就OK了,沙盒的具体目录以下所示:
// 沙盒下的文件夹目录Documents: iTunes会同步此文件夹,不该该删除 Library Caches: 缓存文件夹,删除缓存主要删除的文件夹 Preferences: NSUserDefault写入此文件夹下,iTunes会同步,不该该删除 tmp:系统建立的临时文件夹,随时有可能被删除
作清除缓存功能时,可把Library/Caches文件夹下面的文件所有删除,也能够根据业务的须要删除指定的文件夹。
此篇文章仍是比较偏重于原理,对于具体的使用则没有说太多,具体使用各类各样,可是基本原理是比较统一的。关于数据库CURD
因为咱们不是作服务端开发,因此也没有深究,想要研究的话,可在查阅其余资料。除了上述所说的,可能在具体使用中还要考虑数据库版本迁移,数据库同步等需求。这些因为个人水平所限,也没有提到能够参考我写此文时的一篇参考博文iOS应用架构谈 本地持久化方案及动态部署。而NSUserDefault
、File
使用方法都很简单也都没有介绍。CoreData
太过于重量级,我在项目中也没事实际运用过,这里也没有介绍。