理一下 iOS 本地持久化儲(chǔ)存(側(cè)重?cái)?shù)據(jù)庫(kù)SQLite
來(lái)源: Toyun(@陳思Siming)
鏈接:http://www.reibang.com/p/10a26d01dc84
公司的項(xiàng)目存在已有兩年腋粥,版本也到三點(diǎn)幾了勺届,但是本地持久化數(shù)據(jù)存儲(chǔ)魂莫,始終用的是GVUserDefaults這個(gè)對(duì)NSUserDefaults進(jìn)行了擴(kuò)展的第三方庫(kù)。但隨著業(yè)務(wù)的發(fā)展,需要存儲(chǔ)的地方越來(lái)越多,GVUserDefaults也越來(lái)也不能適應(yīng)需求,當(dāng)我們都忍受不了的時(shí)候扭倾,經(jīng)過(guò)一番商討之后,決定使用FMDB這個(gè)封裝了SQLite3的第三方庫(kù)挽绩。
此篇文章以此為主線膛壹,理一理數(shù)據(jù)庫(kù)和本地化儲(chǔ)存的一些知識(shí),但是此篇文章絲毫沒(méi)有提到CoreData唉堪,喜歡使用CoreData的同學(xué)模聋,這里先說(shuō)聲對(duì)不起了浪費(fèi)了您20s寶貴的時(shí)間。
此篇文章的邏輯如下圖所示:
iOS本地持久化儲(chǔ)存方式概述
說(shuō)起iOS本地化儲(chǔ)存的方式巨坊,大家估計(jì)在也熟悉不過(guò)了撬槽,NSUserDefault、File趾撵,Keychain侄柔、DataBase無(wú)非也就這幾種方式共啃。
NSUserDefault、File:這兩種使用方式都很簡(jiǎn)單暂题,需要注意的一點(diǎn)就是所存儲(chǔ)的對(duì)象都需要遵守并實(shí)現(xiàn)NSCoding協(xié)議中的兩個(gè)方法移剪,適用的范圍也都是一些小規(guī)模數(shù)據(jù),其實(shí)NSUserDefault的底層實(shí)現(xiàn)還是以.plist文件進(jìn)行儲(chǔ)存的薪者。
Keychain:用于儲(chǔ)存一些私密信息纵苛,比如密碼、證書(shū)等等,Keychain里保存的信息不會(huì)因App被刪除而丟失言津,在用戶重新安裝App后依然有效攻人。同樣也適用于應(yīng)用之間數(shù)據(jù)共享。
DataBase:說(shuō)到儲(chǔ)存悬槽,就不能不提DataBase技術(shù)怀吻,移動(dòng)端所用的DBMS一般都是SQLite3,在iOS下初婆,SQLite3的API是純C語(yǔ)言的蓬坡,這樣我們一直以來(lái)面向?qū)ο箝_(kāi)發(fā)的朋友們,突然找不到了對(duì)象磅叛,有點(diǎn)那么的驚慌失措屑咳。好在開(kāi)源社區(qū)出現(xiàn)了像FMDB這樣優(yōu)秀的第三方框架幫我們找回了對(duì)象,同樣蘋果自己也出了新的技術(shù)就是所謂的CoreData(但CoreData并不是此篇文章介紹的重點(diǎn))弊琴。數(shù)據(jù)庫(kù)適合儲(chǔ)存大規(guī)模數(shù)據(jù)训枢,而且查找起來(lái)也比上述方式方便道伟,所以大量?jī)?chǔ)存的時(shí)候操软,還是要?jiǎng)佑脭?shù)據(jù)庫(kù)遗嗽,這也是我們放棄GVUserDefaults的原因。
數(shù)據(jù)庫(kù)的基本知識(shí)
數(shù)據(jù)庫(kù)
首先要有數(shù)據(jù)Data臣缀,然后數(shù)據(jù)多了名字就升級(jí)了稱為數(shù)據(jù)庫(kù)DataBase,這個(gè)時(shí)候就需要有管理系統(tǒng)去管理數(shù)據(jù)庫(kù)也就是DataBaseManagerSystem泻帮,最后佐以DataBaseAdministrator及上述名稱成為了一個(gè)系統(tǒng)就是所謂的DataBaseSystem精置。
Data –> DataBase –> DataBaseManagerSystem –> DataBaseSystem
Data: 數(shù)據(jù)庫(kù)存儲(chǔ)的基本對(duì)象。
DataBase: 說(shuō)起來(lái)數(shù)據(jù)庫(kù)锣杂,大家都有一個(gè)模模糊糊的概念脂倦,不就是一個(gè)存放數(shù)據(jù)的大倉(cāng)庫(kù)嗎,這還有什么好說(shuō)的元莫。其實(shí)這樣理解就已經(jīng)很好了赖阻,但是說(shuō)的更專業(yè)一點(diǎn)就是數(shù)據(jù)庫(kù)是一個(gè)以某種有組織的方式存儲(chǔ)的數(shù)據(jù)集合。
DataBaseManagerSystem: 數(shù)據(jù)庫(kù)管理系統(tǒng)是位于用戶與操作系統(tǒng)之間的一層數(shù)據(jù)管理軟件踱蠢。常見(jiàn)的DBMS有MySQL火欧、PostgreSQL棋电、 Microsoft SQL Server、Oracle苇侵、Sybase赶盔、IBM DB2。當(dāng)然這些都是用于服務(wù)端的DBMS榆浓,移動(dòng)端用的DBMS是SQLite3于未,這也是本文講述的重點(diǎn)。
DataBaseSystem: 在計(jì)算機(jī)系統(tǒng)中引入數(shù)據(jù)庫(kù)后的系統(tǒng)陡鹃,一般由數(shù)據(jù)庫(kù)烘浦,數(shù)據(jù)庫(kù)管理系統(tǒng),應(yīng)用系統(tǒng)萍鲸,數(shù)據(jù)庫(kù)管理成員(DBA)構(gòu)成闷叉。一般情況下稱數(shù)據(jù)庫(kù)系統(tǒng)為數(shù)據(jù)庫(kù),所以有時(shí)候我們就會(huì)感到迷糊猿推,你說(shuō)DBS片习,他以為DB,你轉(zhuǎn)到DB上了蹬叭,他又開(kāi)始講DBS藕咏。所以真正想理解透一些知識(shí)的時(shí)候,基本概念一定要清晰一些秽五,這樣才不容易迷糊孽查。
SQL語(yǔ)句
SQL(Structured Query Language),結(jié)構(gòu)化查詢語(yǔ)言坦喘,專門用來(lái)與數(shù)據(jù)庫(kù)通信的語(yǔ)言盲再。既然要操作數(shù)據(jù)庫(kù),SQL語(yǔ)句是必須要會(huì)寫(xiě)的瓣铣。下面我簡(jiǎn)單列舉幾條簡(jiǎn)單的SQL語(yǔ)句僅供參考答朋。想要學(xué)習(xí)更多的SQL語(yǔ)句的知識(shí),請(qǐng)查閱其他資料棠笑,本篇在這兒不過(guò)多敘述梦碗。
這里以學(xué)生表為例寫(xiě)幾條簡(jiǎn)單的SQL語(yǔ)句
// 創(chuàng)表(table)一張學(xué)生表 表名:student
? 字段id: ?學(xué)生編號(hào),作為主鍵蓖救,類型為整形
? 字段name:學(xué)生名字洪规,類型為字符串,并且不能為空值
? 字段age: 學(xué)生年齡循捺,類型為整形可為空斩例。
? 其中字段又稱之為列(column)
create table if not exists student (
? ?id integer primary key autoincrement,
name text not null,
age integer);
// 刪除學(xué)生表
drop table if exists student;
// 插入一條記錄,主鍵id會(huì)自增長(zhǎng)自動(dòng)賦值从橘,
其中記錄又稱之為行(row)
insert into student (name, age) values ('小明', 20);
// 刪除名字為小明的學(xué)生念赶,
這里的關(guān)鍵字where就是條件础钠,如果無(wú)此條件,則影響整張表
delete from student where name = '小明';
// 更新符合條件的記錄
update student set age = 21 where name = '小明';
// 查詢符合條件的記錄
select * from student where name = '小明';
數(shù)據(jù)庫(kù)的使用
如果你沒(méi)有使用CoreData晶乔,那么無(wú)論你使用的是純C語(yǔ)言的SQLite3庫(kù)珍坊,還是使用對(duì)其進(jìn)行封裝了的FMDB,你都要設(shè)計(jì)出適合自己業(yè)務(wù)的表結(jié)構(gòu)正罢。關(guān)于表的設(shè)計(jì)阵漏,可以有兩種設(shè)計(jì)模式。
數(shù)據(jù)庫(kù)表的設(shè)計(jì)
第一種設(shè)計(jì)方式
以模型中的每個(gè)屬性作為表的一個(gè)字段翻具,這樣在查詢履怯、讀取放面操作起來(lái)更為方便,但是我個(gè)人感覺(jué)這種模式適合的業(yè)務(wù)是記錄條數(shù)不多裆泳。而且字段盡可能的不要更改叹洲。創(chuàng)建好表之后,在去修改表結(jié)構(gòu)還是聽(tīng)麻煩的一件事的工禾。具體使用运提,可參考下面的代碼:
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@end
// 數(shù)據(jù)儲(chǔ)存的路徑
NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *dbpath = [document stringByAppendingPathComponent:@"toyun.sqlite"];
// 鏈接數(shù)據(jù)庫(kù)
self.db = [FMDatabase databaseWithPath:dbpath];
// 打開(kāi)數(shù)據(jù)庫(kù)
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)"];
}
// 實(shí)例化對(duì)象,即要儲(chǔ)存的對(duì)象
NSMutableArray *array = [NSMutableArray array];
for (NSInteger i = 0; i
第二種設(shè)計(jì)方式
這一種設(shè)計(jì)模式是將model作為一個(gè)字段直接將model轉(zhuǎn)為NSData儲(chǔ)在此字段中闻葵,在這里要指出的是model必須實(shí)現(xiàn)NSCoding協(xié)議民泵,不過(guò)實(shí)際項(xiàng)目中我們字典轉(zhuǎn)模型大都是使用第三方庫(kù),而現(xiàn)在比較流行的三個(gè)字典轉(zhuǎn)模型的第三方庫(kù)Mantle槽畔、MJExtension栈妆、YYModel都默認(rèn)對(duì)此進(jìn)行了處理,所以這兒稍微注意一下就好厢钧。第二種方法更適合于記錄條數(shù)比較多鳞尔,和業(yè)務(wù)的相關(guān)性不是太敏感,而且如果有查找排序這樣的需求的話早直,可以從model挑出某些屬性作為表中附帶的一些字段寥假。具體使用,參考下面的代碼:
@interface Student : NSObject
@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;
}
@end
NSString *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
數(shù)據(jù)庫(kù)使用中線程安全
在多線程中操作數(shù)據(jù)庫(kù)霞扬,我們就要考慮線程安全問(wèn)題昧旨,不然就有可能引起數(shù)據(jù)錯(cuò)亂的問(wèn)題。解決辦法有很多祥得,可以自己去加鎖,但是讀寫(xiě)速度要求高的話就不太建議加鎖了蒋得。SQLite3對(duì)于多線程是直接支持的级及,SQLite3庫(kù)提供了三種方式:single thread、multi thread额衙、serialized饮焦。同樣 FMDB自己也提供了多線程操作數(shù)據(jù)庫(kù)的類FMDatabaseQueue怕吴,這個(gè)使用起來(lái)還是比較簡(jiǎn)單的。
刪除緩存
現(xiàn)在大多數(shù)的App都是有本地化儲(chǔ)存的县踢,但是卻不是每個(gè)App都對(duì)應(yīng)有刪除緩存的功能转绷,起碼我們自己的App是沒(méi)有做刪除緩存這個(gè)功能的,緩存是為了節(jié)省流量硼啤,刪除緩存是為了節(jié)省空間我認(rèn)為這兩個(gè)功能同等重要议经,但是這個(gè)需求提了幾次,產(chǎn)品不怎么重視扒捶怠煞肾!特別是在16G的版本下,這個(gè)功能還能能提升一些用戶體驗(yàn)的嗓袱。
此功能到也不難實(shí)現(xiàn)籍救,搞清楚各種儲(chǔ)存方式他們把文件存儲(chǔ)到了那個(gè)文件夾下面,不同的數(shù)據(jù)我們儲(chǔ)存的地方明顯也是不同的渠抹,然后根據(jù)需求刪除對(duì)應(yīng)的數(shù)據(jù)就OK了蝙昙,沙盒的具體目錄如下所示:
// 沙盒下的文件夾目錄
Documents: iTunes會(huì)同步此文件夾,不應(yīng)該刪除
Library
? ?Caches: 緩存文件夾,刪除緩存主要?jiǎng)h除的文件夾
? ?Preferences: NSUserDefault寫(xiě)入此文件夾下梧却,iTunes會(huì)同步奇颠,不應(yīng)該刪除
tmp:系統(tǒng)創(chuàng)建的臨時(shí)文件夾,隨時(shí)有可能被刪除
做清除緩存功能時(shí)篮幢,可把Library/Caches文件夾下面的文件全部刪除大刊,也可以根據(jù)業(yè)務(wù)的需要?jiǎng)h除指定的文件夾。
總結(jié)
此篇文章還是比較偏重于原理三椿,對(duì)于具體的使用則沒(méi)有說(shuō)太多缺菌,具體使用各種各樣,但是基本原理是比較統(tǒng)一的搜锰。關(guān)于數(shù)據(jù)庫(kù)CURD由于我們不是做服務(wù)端開(kāi)發(fā)伴郁,所以也沒(méi)有深究,想要研究的話蛋叼,可在查閱其他資料焊傅。除了上述所說(shuō)的,可能在具體使用中還要考慮數(shù)據(jù)庫(kù)版本遷移狈涮,數(shù)據(jù)庫(kù)同步等需求狐胎。這些由于我的水平所限,也沒(méi)有提到可以參考我寫(xiě)此文時(shí)的一篇參考博文《iOS應(yīng)用架構(gòu)談(4):本地持久化方案及動(dòng)態(tài)部署》歌馍。而NSUserDefault握巢、File使用方法都很簡(jiǎn)單也都沒(méi)有介紹。CoreData太過(guò)于重量級(jí)松却,我在項(xiàng)目中也沒(méi)事實(shí)際運(yùn)用過(guò)暴浦,這里也沒(méi)有介紹溅话。
第二種設(shè)計(jì)的方式挺適用,對(duì)于表較多并且各個(gè)表字段不一歌焦,建議使用表名與實(shí)體類映射飞几,通過(guò)runtime獲取所有實(shí)體類屬性進(jìn)行增刪改查等操作!
?