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支持如下的屬性類型:Bool、Int八、Int1六、Int3二、Int6四、Double、Float、String、NSDate(精度到秒)以及NSData.
(2)也可使用List<object> 和Object來創建諸如一對多、一對一之類的關係模型,此外Object的子類也支持此功能。

4,Realm的安裝配置 
(1)先去Realm的官網去下載最新框架: http://static.realm.io/downloads/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
import  RealmSwift
 
//消費類型
class  ConsumeType : Object  {
     //類型名
     dynamic  var  name =  ""
}
 
//消費條目
class  ConsumeItem : Object  {
     //條目名
     dynamic  var  name =  ""
     //金額
     dynamic  var  cost = 0.00
     //時間
     dynamic  var  date =  NSDate ()
     //所屬消費類別
     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 )
         //已經有記錄的話就不插入了
         if  items.count>0 {
             return
         }
         
         //建立兩個消費類型
         let  type1 =  ConsumeType ()
         type1.name =  "購物"
         let  type2 =  ConsumeType ()
         type2.name =  "娛樂"
         
         //建立三個消費記錄
         let  item1 =  ConsumeItem (value: [ "買一臺電腦" ,5999.00, NSDate (),type1])  //可以使用數組建立
         
         let  item2 =  ConsumeItem ()
         item2.name =  "看一場電影"
         item2.cost = 30.00
         item2.date =  NSDate (timeIntervalSinceNow: -36000)
         item2.type = type2
         
         let  item3 =  ConsumeItem ()
         item3.name =  "買一包泡麪"
         item3.cost = 2.50
         item3.date =  NSDate (timeIntervalSinceNow: -72000)
         item3.type = type1
         
         // 數據持久化操做(類型記錄也會自動添加的)
         try! realm.write {
             realm.add(item1)
             realm.add(item2)
             realm.add(item3)
         }
         
         //打印出數據庫地址
         print (realm.path)
     }
 
     override  func  didReceiveMemoryWarning() {
         super .didReceiveMemoryWarning()
     }
}

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

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


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


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

Realm爲什麼沒法限制查詢數量?
一般查詢數據庫數據時,咱們能夠在sql語句中添加一些限制語句(好比 rownumlimittop等)來限制返回的結果集的行數。
但咱們使用Realm會發現,它沒有這種分頁功能,感受無論查什麼都是把全部的結果都撈出來。好比咱們只要User表的前10條數據,那麼作法是先查詢出全部的User數據,再從結果集中取出前10條數據。
有人可能會擔憂,若是數據庫中數據很是多,那每次都這麼查不會影響性能嗎?
其實大可放心,因爲Realm都是延遲加載的,只有當屬性被訪問時,纔可以讀取相應的數據。不像一般數據庫,查詢後,查詢結果是從數據庫拷貝一份出來放在內存中的。而Realm的查詢結果應該說是數據庫數據的引用,就算你查出來,若是不用也不會佔用什麼內存。

下面咱們把庫裏的數據加載出來,並經過表格顯示出來。
效果圖以下:
原文: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
54
55
import  UIKit
import  RealmSwift
 
class  ViewController UIViewController UITableViewDelegate UITableViewDataSource   {
     
     @IBOutlet  weak  var  tableView:  UITableView !
     
     var  dformatter =  NSDateFormatter ()
     
     //保存從數據庫中查詢出來的結果集
     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!.registerClass( UITableViewCell . self , forCellReuseIdentifier:  "MyCell" )
         
         //使用默認的數據庫
         let  realm = try!  Realm ()
         //查詢全部的消費記錄
         consumeItems = realm.objects( ConsumeItem )
     }
     
     //在本例中,只有一個分區
     func  numberOfSectionsInTableView(tableView:  UITableView ) ->  Int  {
         return  1;
     }
     
     //返回表格行數(也就是返回控件數)
     func  tableView(tableView:  UITableView , numberOfRowsInSection section:  Int ) ->  Int  {
         return  self .consumeItems!.count
     }
     
     //建立各單元顯示內容(建立參數indexPath指定的單元)
     func  tableView(tableView:  UITableView , cellForRowAtIndexPath indexPath:  NSIndexPath )
         ->  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.stringFromDate(item.date)
         return  cell
     }
 
     override  func  didReceiveMemoryWarning() {
         super .didReceiveMemoryWarning()
         // Dispose of any resources that can be recreated.
     }
}

7,支持斷言查詢(Predicate),這樣能夠經過條件查詢特定數據
同時可使用鏈式查詢數據。
1
2
3
4
5
6
7
8
9
//查詢花費超過10元的消費記錄(使用斷言字符串查詢)
consumeItems =  self .realm.objects( ConsumeItem ). filter ( "cost > 10" )
         
//查詢花費超過10元的購物記錄(使用 NSPredicate 查詢)
let  predicate =  NSPredicate (format:  "type.name = '購物' AND cost > 10" )
consumeItems =  self .realm.objects( ConsumeItem ). filter (predicate)
         
//使用鏈式查詢
consumeItems =  self .realm.objects( ConsumeItem ). 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]])。

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

9,使用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 ). filter ( "name contains '小白'" )
ZhangSan .dogs.extend(someDogs)
ZhangSan .dogs.append(dahuang)
反向關係(Inverse Relationship)
經過反向關係(也被稱爲反向連接(backlink)),您能夠經過一個特定的屬性獲取和給定對象有關係的全部對象。好比說,對 Dog 的實例調用Object().linkingObjects(_:forProperty:) 會返回對應的與被調用實例所指定屬性的類有關係的全部對象。經過在 Dog 中定義一個只讀(計算)屬性 owners 能夠簡化這個操做:
1
2
3
4
5
6
7
8
9
class  Dog Object  {
     dynamic  var  name =  ""
     dynamic  var  age = 0
     var  owners: [ Person ] {
         // Realm 並不會存儲這個屬性,由於這個屬性只定義了 getter
         // 定義「owners」,和 Person.dogs 創建反向關係
         return  linkingObjects( Person . self , forProperty:  "dogs" )
     }
}

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

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

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

13,修改更新數據  
(1)直接更新內容
1
2
3
4
// 在一個事務中更新對象
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 更新該書籍
realm.write {
   realm.add(cheeseBook, update:  true )
}
 
/****** 方式2 ***/
// 假設帶有主鍵值 `1` 的「書籍」對象已經存在
realm.write {
   realm.create( Book . self , value: [ "id" : 1,  "price" : 9000.0], update:  true )
// 這本書的`title`屬性不會被改變
}
(3)鍵值編碼 
這個是在運行時才能決定哪一個屬性須要更新的時候,這個對於大量更新的對象極爲有用。
1
2
3
4
5
6
7
let  persons =  Realm ().objects( Person )
Realm ().write {
   // 更新第一個
   persons.first?.setValue( true , forKeyPath:  "isFirst" )
   // 將每一個人的 planet 屬性設置爲「地球」
   persons.setValue( "地球" , forKeyPath:  "planet" )
}

14,刪除數據
1
2
3
4
5
6
let  cheeseBook = ...  // 存儲在 Realm 中的 Book 對象
 
// 在事務中刪除一個對象
realm.write {
   realm.delete(cheeseBook)
}
也可以刪除數據庫中的全部數據
1
2
3
4
// 從 Realm 中刪除全部數據
realm.write {
   realm.deleteAll()
}

15,Realm數據庫配置 
(1)修改默認的的數據庫
經過調用 Realm() 來初始化以及訪問咱們的 realm 變量。其指向的是應用的 Documents 文件夾下的一個名爲「default.realm」的文件。
經過對默認配置進行更改,咱們可使用不一樣的數據庫。好比給每一個用戶賬號建立一個特有的 Realm 文件,經過切換配置,就能夠直接使用默認的 Realm 數據庫來直接訪問各自數據庫:
1
2
3
4
5
6
7
8
9
10
11
func  setDefaultRealmForUser(username:  String ) {
   var  config =  Realm . Configuration ()
 
   // 使用默認的目錄,可是使用用戶名來替換默認的文件名
   config.path = config.path.stringByDeletingLastPathComponent()
                            .stringByAppendingPathComponent(username)
                            .stringByAppendingPathExtension( "realm" )
 
   // 將這個配置應用到默認的 Realm 數據庫當中
   Realm . Configuration .defaultConfiguration = config
}
(2)打包進項目裏的數據庫的使用
若是須要將應用的某些數據(好比配置信息,初始化信息等)打包到一個 Realm 文件中,做爲主要 Realm 數據庫的擴展,操做以下:
1
2
3
4
5
6
7
8
9
10
11
let  config =  Realm . Configuration (
     // 獲取須要打包文件的路徑
     path:  NSBundle .mainBundle().pathForResource( "MyBundledData" , ofType: "realm" ),
     // 以只讀模式打開文件,由於應用數據包並不可寫
     readOnly:  true )
 
// 經過配置打開 Realm 數據庫
let  realm =  Realm (configuration: config)
 
// 經過配置打開 Realm 數據庫
let  results = realm.objects( Dog ). filter ( "age > 5" )
(3)內存數據庫
內存數據庫在每次程序運行期間都不會保存數據。可是,這不會妨礙到 Realm 的其餘功能,包括查詢、關係以及線程安全。 假如您須要靈活的數據讀寫但又不想儲存數據的話,那麼內存數據庫對您來講必定是一個不錯的選擇。
1
let  realm =  Realm (configuration:  Realm . Configuration (inMemoryIdentifier:  "MyInMemoryRealm" ))

16,加密數據庫 
(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
/*****   在建立 Realm 數據庫時採用64位的密鑰對數據庫文件進行 AES-256+SHA2 加密   ****/
// 產生隨機密鑰
let  key =  NSMutableData (length: 64)!
SecRandomCopyBytes (kSecRandomDefault,  UInt (key.length),
     UnsafeMutablePointer < UInt8 >(key.mutableBytes))
 
// 打開加密文件
let  config =  Realm . Configuration (encryptionKey: key)
var  error:  NSError ?
let  realm =  Realm (configuration: config, error: &error)
if  realm ==  nil  {
     // 若是密鑰錯誤,`error` 會提示數據庫不可訪問
     println ( "Error opening realm: \(error)" )
     return
}
 
// 和往常同樣使用 Realm 便可
let  dogs = realm.objects( Dog ). filter ( "name contains 'Fido'" )

17,數據遷移(Migration)
(1)爲什麼要遷移
好比原來有以下 Person 模型:
1
2
3
4
5
class  Person Object  {
     dynamic  var  firstName =  ""
     dynamic  var  lastName =  ""
     dynamic  var  age = 0
}
假如咱們想要更新數據模型,給它添加一個 fullname 屬性, 而不是將「姓」和「名」分離開來。
1
2
3
4
class  Person Object  {
     dynamic  var  fullName =  ""
     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 =  Realm ()
雖然這個遷移操做是最精簡的了,可是咱們須要讓這個閉包可以自行計算新的屬性(這裏指的是 fullName),這樣纔有意義。 在遷移閉包中,咱們可以調用Migration().enumerate(_:_:) 來枚舉特定類型的每一個 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) {
       // enumerate(_:_:) 方法遍歷了存儲在 Realm 文件中的每個「Person」對象
       migration. enumerate ( Person .className()) { oldObject, newObject  in
         // 將名字進行合併,存放在 fullName 域中
         let  firstName = oldObject![ "firstName" as String
         let  lastName = oldObject![ "lastName" as String
         newObject![ "fullName" ] =  "\(firstName) \(lastName)"
       }
     }
   })

18,使用帶有 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
19
20
21
22
23
24
25
{  
  "channels": [
相關文章
相關標籤/搜索