Swift - Realm數據庫的使用詳解(附樣例)


1,什麼是Realm
Realm  於2014 年7月發佈,是一個跨平臺的移動數據庫引擎,專門爲移動應用的數據持久化而生。其目的是要取代   Core Data    SQLite

2,關於Realm,你要知道下面幾點:
(1)使用簡單,大部分常用的功能(比如插入、查詢等)都可以用一行簡單的代碼輕鬆完成,學習成本低。
(2) Realm  不是基於   Core Data,也不是基於   SQLite  封裝構建的。它有自己的數據庫存儲引擎。
(3) Realm  具有良好的跨平臺特性,可以在   iOS    Android  平臺上共同使用。代碼可以使用   Swift    Objective-C 以及   Java 語言來編寫。
(4) Realm  還提供了一個輕量級的數據庫查看工具( Realm Browser)。你也可以用它進行一些簡單的編輯操作(比如插入和刪除操作)  

3,支持的類型
(1) Realm  支持以下的屬性類型: BoolInt8Int16Int32Int64DoubleFloatStringDate(精度到秒)以及 Data.
(2)也可以使用   List<object>   Object  來建立諸如一對多、一對一之類的關係模型,此外   Object  的子類也支持此功能。

4,Realm的安裝配置 
(1)先去   Realm  的官網去下載最新框架: http://static.realm.io/downloads/swift/latest。(或者使用cocoapods下載RealmSwift第三方。最新的使用方法案例官網地址: https://realm.io/cn/docs/swift/latest/
(2)拖拽   RealmSwift.framework   Realm.framework 文件到」 Embedded Binaries」選項中。選中   Copy items if needed 並點擊   Finish
原文:Swift - Realm數據庫的使用詳解(附樣例)


5,將數據插入到數據庫中
下面代碼判斷默認數據庫中是否有數據,如果沒有的話將幾個自定義對像插入到數據庫中。
(1)這裏以個人消費記錄爲例,我們先定義消費類別類,和具體消費記錄類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import   Foundation
import   RealmSwift
 
//消費類型
class   ConsumeType : Object   {
     //類型名
     @objc   dynamic   var   name =  ""
}
 
//消費條目
class   ConsumeItem : Object   {
     //條目名
     @objc   dynamic   var   name =  ""
     //金額
     @objc   dynamic   var   cost = 0.00
     //時間
     @objc   dynamic   var   date =  Date ()
     //所屬消費類別
     @objc   dynamic   var   type: ConsumeType ?
}

(2)判斷數據庫記錄是否爲空,空的話則插入數據庫(這裏以默認數據庫爲例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import   UIKit
import   RealmSwift
 
class   ViewController :  UIViewController   {
     
     override   func   viewDidLoad() {
         super .viewDidLoad()
         
         //使用默認的數據庫
         let   realm = try!  Realm ()
         //查詢所有的消費記錄
         let   items = realm.objects( ConsumeItem . self )
         //已經有記錄的話就不插入了
         if   items.count>0 {
             return
         }
         
         //創建兩個消費類型
         let   type1 =  ConsumeType ()
         type1.name =  "購物"
         let   type2 =  ConsumeType ()
         type2.name =  "娛樂"
         
         //創建三個消費記錄
         let   item1 =  ConsumeItem (value: [ "買一臺電腦" ,5999.00, Date (),type1])  //可使用數組創建
         
         let   item2 =  ConsumeItem ()
         item2.name =  "看一場電影"
         item2.cost = 30.00
         item2.date =  Date (timeIntervalSinceNow: -36000)
         item2.type = type2
         
         let   item3 =  ConsumeItem ()
         item3.name =  "買一包泡麪"
         item3.cost = 2.50
         item3.date =  Date (timeIntervalSinceNow: -72000)
         item3.type = type1
         
         // 數據持久化操作(類型記錄也會自動添加的)
         try! realm.write {
             realm.add(item1)
             realm.add(item2)
             realm.add(item3)
         }
         
         //打印出數據庫地址
         print (realm.configuration.fileURL ??  "" )
     }
     
     override   func   didReceiveMemoryWarning() {
         super .didReceiveMemoryWarning()
     }
}

6,Data類型數據的存取
參考另一篇文章: Swift - Realm數據庫中圖片的插入、讀取(Data類型數據的存儲) 

7,使用Realm Browser查看數據庫  
(1)默認數據庫是應用的   Documents  文件夾下的一個名爲「 default.realm」。
1
2
//打印出數據庫地址
print (realm.configuration.fileURL ??  "" )
(2)使用   Realm Browser 工具可以很方便的對.realm數據庫進行讀取和編輯(在  App Store 中搜索  Realm Browser 即可下載)。
可以看到,上面的幾個對象已經成功的插入到數據庫中來。

原文:Swift - Realm數據庫的使用詳解(附樣例)


原文:Swift - Realm數據庫的使用詳解(附樣例)


8,從數據庫中讀取記錄並顯示到表格中來
(1)通過查詢操作, Realm  將會返回包含   Object  集合的   Results  實例。 Results  的表現和   Array  十分相似,並且包含在   Results  中的對象能夠通過索引下標進行訪問。 
(2)所有的查詢(包括查詢和屬性訪問)在   Realm  中都是延遲加載的,只有當屬性被訪問時,才能夠讀取相應的數據。 
(3)查詢結果並不是數據的拷貝:修改查詢結果(在寫入事務中)會直接修改硬盤上的數據。

下面我們把庫裏的數據加載出來,並通過表格顯示出來。
效果圖如下:
原文:Swift - Realm數據庫的使用詳解(附樣例)


代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import   UIKit
import   RealmSwift
 
class   ViewController :  UIViewController ,  UITableViewDelegate ,  UITableViewDataSource    {
     
     @IBOutlet   weak   var   tableView:  UITableView !
     
     var   dformatter =  DateFormatter ()
     
     //保存從數據庫中查詢出來的結果集
     var   consumeItems: Results < ConsumeItem >?
     
     override   func   viewDidLoad() {
         super .viewDidLoad()
         
         self .dformatter.dateFormat =  "MM月dd日 HH:mm"
         
         self .tableView!.delegate =  self
         self .tableView!.dataSource =  self
         //創建一個重用的單元格
         self .tableView!.register( UITableViewCell . self , forCellReuseIdentifier:  "MyCell" )
         
         //使用默認的數據庫
         let   realm = try!  Realm ()
         //查詢所有的消費記錄
         consumeItems = realm.objects( ConsumeItem . self )
     }
     
     //在本例中,只有一個分區
     func   numberOfSections( in   tableView:  UITableView ) ->  Int   {
         return   1;
     }
     
     //返回表格行數(也就是返回控件數)
     func   tableView(_ tableView:  UITableView , numberOfRowsInSection section:  Int ) ->  Int   {
         return   self .consumeItems!.count
     }
     
     //創建各單元顯示內容(創建參數indexPath指定的單元)
     func   tableView(_ tableView:  UITableView , cellForRowAt indexPath:  IndexPath )
         ->  UITableViewCell   {
         //同一形式的單元格重複使用,在聲明時已註冊
         let   cell =  UITableViewCell (style: .value1, reuseIdentifier:  "MyCell" )
         let   item =  self .consumeItems![indexPath.row]
         cell.textLabel?.text = item.name +  " ¥"   +  String (format:  "%.1f" , item.cost)
         cell.detailTextLabel?.text =  self .dformatter.string(from: item.date)
         return   cell
     }
     
     override   func   didReceiveMemoryWarning() {
         super .didReceiveMemoryWarning()
     }
}

9,查詢前N條數據
Realm無法直接限制查詢數量。所以我們如果想要查出部分數據(比如前5條記錄),也是全部查出來後在結果集中撈取。
1
2
3
4
5
6
//查詢並取出前5條數據
let   dogs = try!  Realm ().objects( Dog . self )
for   i  in   0..<5 {
     let   dog = dogs[i]
     // ...
}
Realm爲何無法限制查詢數量?
通常查詢數據庫數據時,我們可以在sql語句中添加一些限制語句(比如 rownumlimittop等)來限制返回的結果集的行數。
但我們使用Realm會發現,它沒有這種分頁功能,感覺不管查什麼都是把所有的結果都撈出來。比如我們只要User表的前10條數據,那麼做法是先查詢出所有的User數據,再從結果集中取出前10條數據。
有人可能會擔心,如果數據庫中數據非常多,那每次都這麼查不會影響性能嗎?
其實大可放心,由於Realm都是延遲加載的,只有當屬性被訪問時,才能夠讀取相應的數據。不像通常數據庫,查詢後,查詢結果是從數據庫拷貝一份出來放在內存中的。而Realm的查詢結果應該說是數據庫數據的引用,就算你查出來,如果不用也不會佔用什麼內存。

10,支持斷言查詢(Predicate),這樣可以通過條件查詢特定數據
同時可以使用鏈式查詢數據。
1
2
3
4
5
6
7
8
9
//查詢花費超過10元的消費記錄(使用斷言字符串查詢)
consumeItems = realm.objects( ConsumeItem . self ). filter ( "cost > 10" )
 
//查詢花費超過10元的購物記錄(使用 NSPredicate 查詢)
let   predicate =  NSPredicate (format:  "type.name = '購物' AND cost > 10" )
consumeItems = realm.objects( ConsumeItem . self ). filter (predicate)
 
//使用鏈式查詢
consumeItems = realm.objects( ConsumeItem . self ). filter ( "cost > 10" ). filter ( "type.name = '購物'" )
支持的斷言:
  • 比較操作數(comparison operand)可以是屬性名稱或者某個常量,但至少有一個操作數必須是屬性名稱;
  • 比較操作符 ==、<=、<、>=、>、!=, 以及 BETWEEN 支持 int、long、long long、float、double 以及 NSDate 屬性類型的比較,比如說 age == 45;
  • 相等比較 ==以及!=,比如說Results<Employee>().filter("company == %@", company)
  • 比較操作符 == and != 支持布爾屬性;
  • 對於 NSString 和 NSData 屬性來說,我們支持 ==、!=、BEGINSWITH、CONTAINS 以及 ENDSWITH 操作符,比如說 name CONTAINS ‘Ja’;
  • 字符串支持忽略大小寫的比較方式,比如說 name CONTAINS[c] ‘Ja’ ,注意到其中字符的大小寫將被忽略;
  • Realm 支持以下複合操作符:「AND」、「OR」 以及 「NOT」。比如說 name BEGINSWITH ‘J’ AND age >= 32;
  • 包含操作符 IN,比如說 name IN {‘Lisa’, ‘Spike’, ‘Hachi’};
  • ==、!=支持與 nil 比較,比如說 Results<Company>().filter("ceo == nil")。注意到這隻適用於有關係的對象,這裏 ceo 是 Company 模型的一個屬性。
  • ANY 比較,比如說 ANY student.age < 21
  • 注意,雖然我們不支持複合表達式類型(aggregate expression type),但是我們支持對對象的值使用 BETWEEN 操作符類型。比如說,Results<Person>.filter("age BETWEEN %@", [42, 43]])。

11,查詢結果的排序
1
2
//查詢花費超過10元的消費記錄,並按升序排列
consumeItems = realm.objects( ConsumeItem . self ). filter ( "cost > 10" ).sorted(byKeyPath:  "cost" )

12,使用List實現一對多關係
List  中可以包含簡單類型的   Object,表面上和可變的   Array  非常類似。
注意:List 只能夠包含 Object 類型,不能包含諸如String之類的基礎類型。 
如果打算給我們的 Person 數據模型添加一個「dogs」屬性,以便能夠和多個「dogs」建立關係,也就是表明一個 Person 可以有多個 Dog,那麼我們可以聲明一個List類型的屬性:
1
2
3
4
5
6
7
8
9
10
class   Person :  Object   {
     ...  // 其餘的屬性聲明
     let   dogs =  List < Dog >()
}
 
// 這裏我們就可以使用已存在的狗狗對象來完成初始化
let   aPerson =  Person (value: [ "李四" , 30, [aDog, anotherDog]])
 
// 還可以使用多重嵌套
let   aPerson =  Person (value: [ "李四" , 30, [[ "小黑" , 5], [ "旺財" , 6]]])
可以和之前一樣,對 List 屬性進行訪問和賦值:
1
2
3
let   someDogs = realm.objects( Dog . self ). filter ( "name contains '小白'" )
ZhangSan .dogs.append(objectsIn: someDogs)
ZhangSan .dogs.append(dahuang)

反向關係(Inverse Relationship)
通過反向關係(也被稱爲反向鏈接( backlink)),您可以通過一個特定的屬性獲取和給定對象有關係的所有對象。 Realm 提供了「鏈接對象 ( linking objects)」 屬性來表示這些反向關係。藉助鏈接對象屬性,您可以通過指定的屬性來獲取所有鏈接到指定對象的對象。
例如,一個 Dog 對象可以擁有一個名爲 owners 的鏈接對象屬性,這個屬性中包含了某些 Person 對象,而這些 Person 對象在其 dogs 屬性中包含了這一個確定的 Dog 對象。您可以將 owners 屬性設置爲   LinkingObjects  類型,然後指定其關係,說明其當中包含了 Person 對象。
1
2
3
4
5
6
7
8
class   Dog :  Object   {
     @objc   dynamic   var   name =  ""
     @objc   dynamic   var   age = 0
     
     // Realm 並不會存儲這個屬性,因爲這個屬性只定義了 getter
     // 定義「owners」,和 Person.dogs 建立反向關係
     let   owners =  LinkingObjects (fromType:  Person . self , property:  "dogs" )
}

13,添加主鍵(Primary Keys) 
重寫   Object.primaryKey() 可以設置模型的主鍵。
聲明主鍵之後,對象將被允許查詢,更新速度更加高效,並且要求每個對象保持唯一性。
一旦帶有主鍵的對象被添加到 Realm 之後,該對象的主鍵將不可修改。
1
2
3
4
5
6
7
8
class   Person :  Object   {
     @objc   dynamic   var   id = 0
     @objc   dynamic   var   name =  ""
     
     override   static   func   primaryKey() ->  String ? {
         return   "id"
     }
}

14,添加索引屬性(Indexed Properties)
重寫   Object.indexedProperties() 方法可以爲數據模型中需要添加索引的屬性建立索引:
1
2
3
4
5
6
7
8
class   Book :  Object   {
     @objc   dynamic   var   price = 0
     @objc   dynamic   var   title =  ""
     
     override   static   func   indexedProperties() -> [ String ] {
         return   [ "title" ]
     }
}

15,設置忽略屬性(Ignored Properties)
重寫  Object.ignoredProperties()  可以防止 Realm 存儲數據模型的某個屬性。Realm 將不會干涉這些屬性的常規操作,它們將由成員變量( var)提供支持,並且您能夠輕易重寫它們的 setter 和 getter。
1
2
3
4
5
6
7
8
9
10
11
12
class   Person :  Object   {
     @objc   dynamic   var   tmpID = 0
     var   name:  String   {  // 計算屬性將被自動忽略
         return   "\(firstName) \(lastName)"
     }
     @objc   dynamic   var   firstName =  ""
     @objc   dynamic   var   lastName =  ""
     
     override   static   func   ignoredProperties() -> [ String ] {
         return   [ "tmpID" ]
     }
}

16,修改更新數據 
(1)直接更新內容
1
2
3
4
// 在一個事務中更新對象
try! realm.write {
   consumeItem.name =  "去北京旅行"
}
(2)通過主鍵更新
如果您的數據模型中設置了主鍵的話,那麼您可以使用   Realm().add(_:update:) 來更新對象(當對象不存在時也會自動插入新的對象。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/****** 方式1 ***/
// 創建一個帶有主鍵的「書籍」對象,作爲事先存儲的書籍
let   cheeseBook =  Book ()
cheeseBook.title =  "奶酪食譜"
cheeseBook.price = 9000
cheeseBook.id = 1
 
// 通過 id = 1 更新該書籍
try! realm.write {
     realm.add(cheeseBook, update:  true )
}
 
/****** 方式2 ***/
// 假設帶有主鍵值 `1` 的「書籍」對象已經存在
try! realm.write {
     realm.create( Book . self , value: [ "id" : 1,  "price" : 22], update:  true )
     // 這本書的`title`屬性不會被改變
}
(3)鍵值編碼 
這個是在運行時才能決定哪個屬性需要更新的時候,這個對於大量更新的對象極爲有用。
1
2
3
4
5
6
7
let   persons = realm.objects( Person . self )
try! realm.write {
     // 更新第一個
     persons.first?.setValue( true , forKeyPath:  "isFirst" )
     // 將每個人的 planet 屬性設置爲「地球」
     persons.setValue( "地球" , forKeyPath:  "planet" )
}

17,刪除數據
1
2
3
4
5
6
let   cheeseBook = ...  // 存儲在 Realm 中的 Book 對象
 
// 在事務中刪除一個對象
try! realm.write {
   realm.delete(cheeseBook)
}
也能夠刪除數據庫中的所有數據
1
2
3
4
// 從 Realm 中刪除所有數據
try! realm.write {
   realm.deleteAll()
}

18,Realm數據庫配置 
(1)修改默認的的數據庫
通過調用   Realm() 來初始化以及訪問我們的   realm  變量。其指向的是應用的   Documents  文件夾下的一個名爲「 default.realm」的文件。
通過對默認配置進行更改,我們可以使用不同的數據庫。比如給每個用戶帳號創建一個特有的 Realm 文件,通過切換配置,就可以直接使用默認的 Realm 數據庫來直接訪問各自數據庫:
1
2
3
4
5
6
7
8
9
10
func   setDefaultRealmForUser(username:  String ) {
     var   config =  Realm . Configuration ()
     
     // 使用默認的目錄,但是使用用戶名來替換默認的文件名
     config.fileURL = config.fileURL!.deletingLastPathComponent()
         .appendingPathComponent( "\(username).realm" )
     
     // 將這個配置應用到默認的 Realm 數據庫當中
     Realm . Configuration .defaultConfiguration = config
}
(2)打包進項目裏的數據庫的使用
如果需要將應用的某些數據(比如配置信息,初始化信息等)打包到一個   Realm  文件中,作爲主要   Realm  數據庫的擴展,操作如下:
1
2
3
4
5
6
7
8
9
10
11
let   config =  Realm . Configuration (
     // 獲取需要打包文件的 URL 路徑
     fileURL:  Bundle .main.url(forResource:  "MyBundledData" , withExtension:  "realm" ),
     // 以只讀模式打開文件,因爲應用數據包並不可寫
     readOnly:  true )
 
// 通過配置打開 Realm 數據庫
let   realm = try!  Realm (configuration: config)
 
// 通過配置打開 Realm 數據庫
let   results = realm.objects( Dog . self ). filter ( "age > 5" )
(3)內存數據庫
內存數據庫在每次程序運行期間都不會保存數據。但是,這不會妨礙到   Realm  的其他功能,包括查詢、關係以及線程安全。 假如您需要靈活的數據讀寫但又不想儲存數據的話,那麼內存數據庫對您來說一定是一個不錯的選擇。
1
let   realm = try!  Realm (configuration:  Realm . Configuration (inMemoryIdentifier:  "MyInMemoryRealm" ))

19,加密數據庫 
(1)加密後的   Realm文件不能跨平臺使用(因爲   NSFileProtection  只有   iOS  纔可以使用) 
(2) Realm  文件不能在沒有密碼保護的   iOS  設備中進行加密。爲了避免這些問題(或者您想構建一個   OS X 的應用),可以使用   Realm  提供的加密方法。 
(3)加密過的   Realm  只會帶來很少的額外資源佔用(通常最多隻會比平常慢10%)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*****   在創建 Realm 數據庫時採用64位的密鑰對數據庫文件進行 AES-256+SHA2 加密   ****/
// 產生隨機密鑰
var   key =  Data (count: 64)
_ = key.withUnsafeMutableBytes { bytes  in
     SecRandomCopyBytes (kSecRandomDefault, 64, bytes)
}
         
// 打開加密文件
let   config =  Realm . Configuration (encryptionKey: key)
let   realm: Realm
do {
     realm = try  Realm (configuration: config)
} catch  let   error  as   NSError   {
     // 如果密鑰錯誤,`error` 會提示數據庫不可訪問
     fatalError( "Error opening realm: \(error)" )
}
 
// 和往常一樣使用 Realm 即可
let   dogs = realm.objects( Book . self ). filter ( "name contains 'Fido'" )

20,數據遷移(Migration)
(1)爲何要遷移
比如原來有如下   Person  模型:
1
2
3
4
5
class   Person :  Object   {
     @objc   dynamic   var   firstName =  ""
     @objc   dynamic   var   lastName =  ""
     @objc   dynamic   var   age = 0
}
假如我們想要更新數據模型,給它添加一個   fullname  屬性, 而不是將「姓」和「名」分離開來。
1
2
3
4
class   Person :  Object   {
     @objc   dynamic   var   fullName =  ""
     @objc   dynamic   var   age = 0
}
在這個時候如果您在數據模型更新之前就已經保存了數據的話,那麼   Realm  就會注意到代碼和硬盤上數據不匹配。 每當這時,您必須進行數據遷移,否則當您試圖打開這個文件的話   Realm  就會拋出錯誤。  

(2)如何進行數據遷移
假設我們想要把上面所聲明   Person  數據模型進行遷移。如下所示是最簡單的數據遷移的必需流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 在(application:didFinishLaunchingWithOptions:)中進行配置
 
let   config =  Realm . Configuration (
   // 設置新的架構版本。這個版本號必須高於之前所用的版本號
   // (如果您之前從未設置過架構版本,那麼這個版本號設置爲 0)
   schemaVersion: 1,
 
   // 設置閉包,這個閉包將會在打開低於上面所設置版本號的 Realm 數據庫的時候被自動調用
   migrationBlock: { migration, oldSchemaVersion  in
     // 目前我們還未進行數據遷移,因此 oldSchemaVersion == 0
     if   (oldSchemaVersion < 1) {
       // 什麼都不要做!Realm 會自行檢測新增和需要移除的屬性,然後自動更新硬盤上的數據庫架構
     }
   })
 
// 告訴 Realm 爲默認的 Realm 數據庫使用這個新的配置對象
Realm . Configuration .defaultConfiguration = config
 
// 現在我們已經告訴了 Realm 如何處理架構的變化,打開文件之後將會自動執行遷移
let   realm = try!  Realm ()
雖然這個遷移操作是最精簡的了,但是我們需要讓這個閉包能夠自行計算新的屬性(這裏指的是   fullName),這樣纔有意義。 在遷移閉包中,我們能夠調用 Migration().enumerateObjects(_:_:) 來枚舉特定類型的每個 Object 對象,然後執行必要的遷移邏輯。注意,對枚舉中每個已存在的   Object  實例來說,應該是通過訪問   oldObject  對象進行訪問,而更新之後的實例應該通過   newObject  進行訪問:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在 application(application:didFinishLaunchingWithOptions:) 中進行配置
 
Realm . Configuration .defaultConfiguration =  Realm . Configuration (
   schemaVersion: 1,
   migrationBlock: { migration, oldSchemaVersion  in
     if   (oldSchemaVersion < 1) {
       // enumerateObjects(ofType:_:) 方法遍歷了存儲在 Realm 文件中的每一個「Person」對象
       migration.enumerateObjects(ofType:  Person .className()) { oldObject, newObject  in
         // 將名字進行合併,存放在 fullName 域中
         let   firstName = oldObject![ "firstName" ]  as !  String
         let   lastName = oldObject![ "lastName" ]  as !  String
         newObject![ "fullName" ] =  "\(firstName) \(lastName)"
       }
     }
   })

21,使用帶有 REST API 功能的 Realm 數據庫示例
我們將從 豆瓣FM的API 那裏獲取一組   JSON  格式的頻道數據,然後將它以   Realm Objects  的形式儲存到默認的   Realm  數據庫裏。 
(1)json數據格式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
相關文章
相關標籤/搜索