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:) 中進行配置
相關文章
相關標籤/搜索