在前一篇文章《嘗鮮Realm》,我們了解到Realm標(biāo)榜其為專注于移動(dòng)平臺(tái)的數(shù)據(jù)庫(kù),那既然是數(shù)據(jù)庫(kù),我們當(dāng)然要看最基本的CRUD(Create/Read/Update/Delete)操作。當(dāng)然在做這些基本的操作之前還是需要學(xué)習(xí)下Realm的基本知識(shí)。
0. Realm與我們熟悉的SQL
當(dāng)使用SQL(SQLite)時(shí),首先我們會(huì)設(shè)計(jì)關(guān)系數(shù)據(jù)庫(kù)的結(jié)構(gòu),比如設(shè)計(jì)幾個(gè)表,每個(gè)表里面哪些是主鍵、對(duì)哪些鍵做索引、默認(rèn)值是什么等等,并將這些用SQL表示好,然后調(diào)用函數(shù)執(zhí)行創(chuàng)建表的SQL語(yǔ)句或者通過(guò)一些ORM工具如:FMDB的函數(shù)創(chuàng)建表格,然后還要對(duì)應(yīng)的創(chuàng)建一個(gè)class/struct來(lái)表示這個(gè)數(shù)據(jù)結(jié)構(gòu)。而Realm拋棄了這繁瑣的中間語(yǔ)言,而采用目標(biāo)語(yǔ)言(比如Objective-C/Swift/Java)本身作為DSL(domain-specific language)來(lái)描述同樣的類似表格的數(shù)據(jù)結(jié)構(gòu),由于是目標(biāo)語(yǔ)言,其自身就能表達(dá)一個(gè)數(shù)據(jù)結(jié)構(gòu),因此帶有了一些Model層屬性,比如SQL很難描述某列的屬性是另一個(gè)結(jié)構(gòu),需要自己定義數(shù)據(jù)結(jié)構(gòu)來(lái)表示,而Realm的DSL則自動(dòng)包含了這層定義。
我們來(lái)看個(gè)例子,描述省與城市的天氣:省有其天氣屬性,同時(shí)還有其包含的城市;而城市也有天氣屬性。用SQL的話,我們可能會(huì)設(shè)計(jì)如下三個(gè)表:
// table t_province
CREATE TABLE t_provience (
f_provience_id int,
f_proviecne_name varchar(255),
f_weather varchar(255)
PRIMARY KEY (f_province_id)
)
// table f_city
CREATE TABLE t_city (
f_city_id int,
f_city_name varchar(255),
f_city varchar(255)
PRIMARY KEY (f_city)
)
// relationship of city and province
CREATE TABLE t_province_city (
f_provience_id int,
f_city_id int,
PRIMARY KEY (f_proviece_id)
)
表t_province表示省份信息,表f_city表示城市信息,這里為了凸顯要表示的二者之間的關(guān)系,用了一個(gè)表t_province_city來(lái)表示一個(gè)省有幾個(gè)城市。然后在程序中可能還要定義兩個(gè)結(jié)構(gòu)Province和City。
那同樣的意思如何用Realm來(lái)表示呢?
用Realm的話只需要定義兩個(gè)結(jié)構(gòu)就可以了:
@interface City : RLMObject
@property int id;
@property NSString *name;
@end
@interface Province : RLMObject
@property int id;
@property NSString *name;
@property RLMArray<City *><City> *cities;
@end
這里定義了兩個(gè)結(jié)構(gòu)(Model):Provience和City。首先他們都需要繼承Realm的RLMObject。City定義了name和id屬性;Province除了定義了name和id還定義了了一個(gè)cities成員,而且其定義十分詭異:
-
RLMArray: 表示一個(gè)RLMArray對(duì)象,可以認(rèn)為是一個(gè)NSArray對(duì)象 -
<City *>: 可以認(rèn)為是每個(gè)Array成員的類型. -
<City>: 序列化的結(jié)構(gòu),按照找個(gè)類定義的成員和相關(guān)輔助函數(shù)來(lái)做序列化操作.
所以上面就是定義了: 一個(gè)按照結(jié)構(gòu)City進(jìn)行存儲(chǔ)的City *的數(shù)組的指針。
其實(shí)就可以認(rèn)為是City對(duì)象的數(shù)組就可以了。只是寫(xiě)法比較詭異。因?yàn)橛羞@個(gè)數(shù)組的存在來(lái)維護(hù)“1 to n"的關(guān)系,所以也就不需要SQL里面定義的關(guān)系表了,而且已經(jīng)用目標(biāo)語(yǔ)言對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行了描述。
這樣看來(lái)是不是覺(jué)得Realm會(huì)更簡(jiǎn)單呢?
1. 學(xué)生信息管理Demo
看了上面還不夠,我們來(lái)看個(gè)例子,看看是不是操作簡(jiǎn)單、容易理解并且動(dòng)作高效。
這里我準(zhǔn)備了一個(gè)學(xué)生信息管理界面(實(shí)際上就一個(gè)學(xué)生名和年紀(jì)),可以添加、查詢、搜索以及刪除學(xué)生:

代碼主要是由OC來(lái)實(shí)現(xiàn)的(下面的接口也是以O(shè)C為例講解),具體的demo可以在github進(jìn)行下載。
2. Realm
realm的操作均由一個(gè)RLMRealm來(lái)管理,我們稱之為realm,可以把他理解為一個(gè)數(shù)據(jù)庫(kù)。而其他繼承自RLMObject都是一個(gè)個(gè)的數(shù)據(jù)表。通過(guò):
+ (nonnull instancetype)defaultRealm;
可以得到一個(gè)默認(rèn)的default的Realm對(duì)象,其將會(huì)在默認(rèn)的位置:
- iOS: App路徑的Document目錄下,比如:/data/Containers/Data/Application/5232B434-092D-4B79-827F-1729BB417144/Documents/default.realm
- MacOS: 系統(tǒng)的Application Support 目錄,比如:/Users/apollo/Library/Application%20Support/firstblood/default.realm
當(dāng)然通常情況下還是需要?jiǎng)?chuàng)建我們自己的Realm,比如需要放在多個(gè)不同的文件中:
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
URLByAppendingPathComponent:@"crud"]
URLByAppendingPathExtension:@"realm"];
NSLog(@"Realm file path: %@", config.fileURL);
NSError *error;
_realm = [RLMRealm realmWithConfiguration:config error:&error];
這里我們首先獲取了默認(rèn)的RLMRealmConfiguration,然后修改文件路徑為一個(gè)新的NSURL,最后調(diào)用:
+ (nullable instancetype)
realmWithConfiguration:(nonnull RLMRealmConfiguration *)configuration
error:(NSError *_Nullable *_Nullable)error;
創(chuàng)建Realm,它會(huì)在“/data/Containers/Data/Application/5232B434-092D-4B79-827F-1729BB417144/Documents/crud.realm”創(chuàng)建數(shù)據(jù)庫(kù)文件。這樣后面的數(shù)據(jù)存儲(chǔ)就都在這個(gè)文件中了。
3. Create
有了數(shù)據(jù)庫(kù)(Realm)后,我們就可以開(kāi)始創(chuàng)建表了。
首先關(guān)于表的描述,直接繼承RLMObject即可,和繼承NSObject一樣,依次定義其成員即可,每個(gè)成員就是表中的一個(gè)屬性,其類型就是基本類型包括有BOOL, bool, int, NSInteger, long, long long, float, double, NSString, NSDate, NSData等。不過(guò)和普通NSObject不同的是,這里屬性不用指定其屬性類型比如strong、weak等,Realm會(huì)管理器內(nèi)存結(jié)構(gòu)。比如我們建立一個(gè)表示學(xué)生的表:
@interface Student : RLMObject
@property int age;
@property NSString *name;
@end
表示了一個(gè)表Student,其有兩個(gè)屬性,一個(gè)int的age和一個(gè)NSString的name。定義好結(jié)構(gòu)后,來(lái)看如何創(chuàng)建一條記錄:
// (1) Create a Student object and then set its properties
Student *student = [[Student alloc] init];
student.name = @"Jim";
student.age = 10;
// (2) Create a Student object from a dictionary
Student *student2 = [[Student alloc] initWithValue:@{@"name" : @"Tom", @"age" : @3}];
// (3) Create a Student object from an array
Student *student3 = [[Student alloc] initWithValue:@[@"Alex", @3]];
如上,除了第一種和通常的NSObject一樣的創(chuàng)建對(duì)象方式以外還有有另外兩種方式可以實(shí)現(xiàn)。創(chuàng)建完記錄后調(diào)用:
[_realm beginWriteTransaction];
[_realm addObject:s];
[_realm commitWriteTransaction];
這里_realm是你自己創(chuàng)建的Realm數(shù)據(jù)庫(kù),調(diào)用:
- (void)addObject:(nonnull RLMObject *)object;
將記錄添加到數(shù)據(jù)庫(kù)中存儲(chǔ)。"beginWriteTransaction"和“commitWriteTransaction”可以認(rèn)為和SQLite中的commit類似,用來(lái)表示一次存儲(chǔ)事務(wù),當(dāng)commit后,相關(guān)數(shù)據(jù)就表示已經(jīng)落地了。
4. Read
不論是使用CoreData也好,用FMDB封裝的SQLite也好,Query總是最復(fù)雜的操作,要構(gòu)建查詢條件,又要解析查詢結(jié)果,實(shí)在是煩。如果這時(shí)候你看一眼Realm的文檔,發(fā)現(xiàn)Query部分只有一段話,而且沒(méi)有子主題,這個(gè)時(shí)候是不是應(yīng)該竊喜呢?
哈哈,其實(shí)也不是,是查詢就逃不了先查詢?cè)俳馕鼋Y(jié)果的過(guò)程。而Realm的結(jié)果統(tǒng)一是一個(gè)RLMResults對(duì)象。其實(shí)質(zhì)和那個(gè)惡心的數(shù)組一樣是一個(gè)容器對(duì)象,所以也需要指定其內(nèi)部存儲(chǔ)的是什么類型的值:
RLMResults<RLMObject *> *result
需要給定一個(gè)繼承自RLMObject的對(duì)象來(lái)表示其結(jié)果內(nèi)存放的是這個(gè)數(shù)據(jù)類型,其實(shí)也就是你要查詢的表啦。查詢有兩種方式:
-
select * from
從表中查詢到所有結(jié)果://+ (nonnull RLMResults *)allObjects will operate on default realm RLMResults<Student *> *allStudents = [Student allObjectsInRealm:_realm];通過(guò)調(diào)用RLMObject的類方法
+ (nonnull RLMResults *)allObjectsInRealm:(nonnull RLMRealm *)realm;在指定的數(shù)據(jù)庫(kù)Realm中查詢表中的結(jié)果,而+ (nonnull RLMResults *)allObjects;則是在默認(rèn)的Realm中查詢。這里很顯然,得到的結(jié)果就是這里指定的RLMObject結(jié)果,這里是Student。 -
select * from table where ""
如果要查詢符合條件的結(jié)果,則使用:// + (nonnull RLMResults *)objectsWhere:(nonnull NSString *)predicateFormat, ...; will operate on default realm NSString *filter = [NSString stringWithFormat:@"age > %d", [_filterTF.text intValue ]]; RLMResults<Student *> *allStudents = [Student objectsInRealm:_realm where:filter];通過(guò)調(diào)用RLMObject的類方法
+ (nonnull RLMResults *)objectsInRealm:(nonnull RLMRealm *)realm where:(nonnull NSString *)predicateFormat, ...;提供一個(gè)過(guò)濾條件得到和上面一樣的“RLMResults<Student *> ”結(jié)果。同樣也有一個(gè)操作默認(rèn)Realm的+ (nonnull RLMResults *)objectsWhere:(nonnull NSString *)predicateFormat, ...;。這里過(guò)濾條件比較貼近于自然語(yǔ)言,直接用成員名加上">"、“<”等類C的關(guān)系描述符就可以了。
在查詢中構(gòu)建的查詢條件可以使用一些限定詞來(lái)構(gòu)建:
- 造作對(duì)象必須是RLMObject的一個(gè)屬性
- 對(duì) int, long, long long, float, double, 以及 NSDate屬性支持 ==, <=, <, >=, >, !=, 和 BETWEEN操作,比如:age == 45
- 對(duì)象的判等(這個(gè)厲害,直接比較對(duì)象,SQL只能望其項(xiàng)背)。==和!=。 比如 [Employee objectsWhere:@"company == %@", company]
- 對(duì)于BOOL類型支持 ==和!= 判等操作
- 對(duì)于字符串(NSString)類型,支持==, !=, BEGINSWITH, CONTAINS, 以及 ENDSWITH操作,比如: name CONTAINS ‘Ja’。是不是比SQL更智能
- CONTAINS操作是區(qū)分大小寫(xiě)的
- 短路表達(dá)式也是支持的,用“AND”, “OR”, 和 “NOT”分別表示&&、|| 、!
- Realm提供了一個(gè)"IN" 操作來(lái)判斷元素是否在列表中,比如:name IN {‘Lisa’, ‘Spike’, ‘Hachi’}
- Realm提供了“ANY”關(guān)鍵字來(lái)組合條件語(yǔ)句,比如: ANY student.age < 21
- 類似Limit,Realm提供了@count, @min, @max, @sum 和 @avg來(lái)表示結(jié)果的數(shù)目,對(duì)結(jié)果求最大、最小、和以及平均,比如:[Company objectsWhere:@"employees.@count > 5"]等同于"Limit 5"
5. Update
既然上面一直在宣導(dǎo)Realm直接用目標(biāo)語(yǔ)言DSL來(lái)描述數(shù)據(jù)結(jié)構(gòu),而不是用SQL,不用再構(gòu)造數(shù)據(jù)結(jié)構(gòu)然后設(shè)計(jì)SQL語(yǔ)句,這里我們從Update中再來(lái)體味其深意。
假設(shè)我們要更新某個(gè)Student對(duì)象的age屬性,只要通過(guò)上面的“?Read”先查詢出目標(biāo)Stduent對(duì)象,然后執(zhí)行:
[_realm beginWriteTransaction];
_student.age = _ageTF.text.intValue;
[_realm commitWriteTransaction];
即可修改該Student對(duì)象的age為輸入值。"commitWriteTransaction"會(huì)保證落體的物理數(shù)據(jù)被修改。
6. Delete
對(duì)上面的操作都了解了的話,刪除操作其實(shí)非常簡(jiǎn)單:
[_realm beginWriteTransaction];
[_realm deleteObject:s];
[_realm commitWriteTransaction];
首先獲得要?jiǎng)h除的Student對(duì)象,這個(gè)過(guò)程就相當(dāng)于SQL里面刪除時(shí)候的where限制。當(dāng)然這里說(shuō)的Student是Demo中的,在具體應(yīng)用中就是繼承了RLMObject的對(duì)象。然后在事務(wù)提交中調(diào)用 :
- (void)deleteObject:(nonnull RLMObject *)object;
即可影響到具體物理文件里面的記錄,是不是很簡(jiǎn)單,其實(shí)刪除操作本來(lái)也沒(méi)有特殊的地方。
7. 總結(jié)
通過(guò)一個(gè)demo例子中展示的Realm的CRUD操作,我們會(huì)發(fā)現(xiàn),首先不能完全用SQL的經(jīng)驗(yàn)來(lái)設(shè)計(jì)Realm,比如他是可以直接通過(guò)對(duì)象來(lái)表示兩組數(shù)據(jù)之間的"1 to n"、“1 to 1”等關(guān)系的,另外操作直接針對(duì)一個(gè)RLMObject對(duì)象來(lái)進(jìn)行的,再不用SQL來(lái)構(gòu)造語(yǔ)義了,其擴(kuò)展的對(duì)象的判等、BEGINSWITH、 CONTAINS字符串操作等特性更是牛逼的不行。但同時(shí)SQL的經(jīng)驗(yàn)有可以運(yùn)用到對(duì)Realm的使用中。這里只介紹了基本的Realm的API來(lái)實(shí)現(xiàn)CRUD操作,Reaml還提供了諸如通知、動(dòng)態(tài)更新、調(diào)試等高級(jí)功能,我們?cè)傧乱黄?a href="http://www.itdecent.cn/p/52a9f84b158f" target="_blank">《進(jìn)階Realm》繼續(xù)學(xué)習(xí)。