2017年版Realmのエレガントな使い方

  • 21
    いいね
  • 0
    コメント

はじめに

Realmと言えば、とにかく高速なモバイルDBという感じでしたが、最近では「双方向編集」、「リアルタイム同期」など、公式サイトのキャッッチフレーズも変わってきてます。2017年版のハマりポイントと、エレガントな使い方のパターンの紹介です

1.検索してから更新する

下記のように、RLMResultsをインスタンス変数に持った状態で、HogeHogeオブジェクトを追加すると、resultsに自動的に追加されます。

self.results = HogeHoge.allObjects()

# 適当にオブジェクトを生成
 let realm = RLMRealm.default()
 try realm.transaction {
    let hoge = HogeHoge()
    realm.addOrUpdate(draft)
 }

self.results.count #増えてる

逆に、トランザクションの直後に再度取得した場合、反映されないことがありました。

 let realm = RLMRealm.default()
 try realm.transaction {
    let hoge = HogeHoge()
    realm.addOrUpdate(draft)
 }
let results = HogeHoge.allObjects() //書いたオブジェクトがない!!!!

またRLMResultsには更新された際に、発火するブロックを追加できます。


let token = results.addNotificationBlock({[weak self] (results, change, error) in
}

まとめると、Realmで更新直後に、更新内容を含む、データを取得したい場合は

// 1.検索する
self.results = HogeHoge.allObjects()


// 2.更新された際のブロックを追加

self.results.addNotificationBlock({[weak self] (results, change, error) in

}

// 3.更新する
 let realm = RLMRealm.default()
 try realm.transaction {
    let hoge = HogeHoge()
    realm.addOrUpdate(draft)
 }

とするとできます。

2.TableView/CollectionView パターン

TableViewとCollectionViewの場合の実装パターンです

1.viewDidLoadで、RLMResultsをインスタンス変数に持ち、更新フックで、reloadDataを呼び出します

override func viewDidLoad() {
   self.datas = HogeHoge.allObjects() 
   self.datas.addNotificationBlock({[weak self] (results, change, error) in
      self.collectionView.reloadData()
   }
}

2.DataSourceで、RLMResultsを使って、行数を返します

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
   return self.datas.count
}

このように実装すると、データが更新された際に、ビューが自動的に更新されます。

3.PageViewControllerパターン

1ページを表すUIViewControllerにRLMObjectをコピーしたstructを作成し、structのインスタンスを変数として持ちます。RLMObjectを直接保持すると、そのRLMObjectが削除された後に、変数にアクセスするとAssertionErrorが発生します。

class FooViewController: UIViewController {
   var hoge: HogeHogeWrapper!
}

次にDataSourceクラスにて下記のように実装します。

var datas: RLMResults<RLMObject>

override init() {
   datas = HogeHoge.allObjects()
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

   //UIViewControllerから現在のオブジェクトを取得
   guard let hoge  = (viewController as? FooViewController)?.hoge else { return nil }

   //indexを求める
   let index = indexOf(medium: hoge)
   if index <= 0 { return nil}

   //隣のオブジェクトをセットする
   controller.hoge = datas.object(at: (index-1)) as! Hoge
   return controller
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
   guard let hoge  = (viewController as? FooViewController)?.hoge else { return nil }
   let index = indexOf(medium: hoge)
   if (index+1) >= datas.count { return nil }
   controller.hoge = datas.object(at: (index+1)) as! Hoge
   return controller 
}

今のコントローラの持つ、オブジェクトを元に、RLMResultsのindexを求めることで、データが追加された際に、自動的に追従できるようになります。ただしこのままでは、ページ1とページ2がある場合に、データがページの間に追加された場合に、追従しないため更新フックを使って、ページビューを再作成する必要がありますが、大体のケースでは機能します。

現場からは以上です