はじめに
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がある場合に、データがページの間に追加された場合に、追従しないため更新フックを使って、ページビューを再作成する必要がありますが、大体のケースでは機能します。
現場からは以上です。