Realm iOS Code Review

byUSAMI Kosuke

9ヶ月前 (2017/03/26)にアップロードinテクノロジー

共有

USAMI Kosuke さんの他のスライド

もっと見る»

関連するスライド

関連するタグ

このスライドの内容

タップすると表示します
  1. 1.
    Realm iOS を使うときの
    ハマりポイント
    USAMI Kosuke
    (Fenrir Inc.)
  2. 2.
    自己紹介
    » 宇佐見 公輔(@usamik26
    » フェンリル株式会社
    » iOS アプリ開発エンジニア
    » Swift
    » Xamarin.iOS
    » 好きなライブラリは RxSwift
  3. 3.
    Realm
    » 最近 1∼2 年で関わっている開発案件のほとんどで Realm 採用
    » Realm Objective-C
    » Realm Swift
    » Realm Xamarin
  4. 4.
    なぜ Realm なのか?
  5. 5.
    なぜ Realm なのか? その前に…
    » そもそも、アプリにデータベース(DB)を内蔵する必要があるか?
    » DB なんて面倒そう…
    » 他にもっと簡単な方法があるのでは…
  6. 6.
    データベースは問題解決の一手段
    » こんなことがしたい
    » サーバーから取得したデータをしばらく置いておきたい
    » オフラインでも前回取得したデータで動作させたい
    » マスターデータを内蔵したい
    » データベースはそれを解決する手段のひとつにすぎない
  7. 7.
    Realm はそれらの優れた解決手段
    » 組み込みが簡単
    » コーディングが簡単
    » だから、カジュアルな用途に使うのが苦にならない
  8. 8.
    ハマりポイント
    » Realm を採用するプロジェクトが増えた
    » したがって、それをコードレビューする機会が増えた
    » レビューした中で、よくあるハマりポイントを挙げてみたい
    » 実際にはドキュメント読んで正しく扱えば大丈夫
  9. 9.
    ファイルの置き場所
  10. 10.
    Realm ファイルの置き場所
    » デフォルトでは Documents フォルダ内に作られる
    » いろいろ試したい時に便利
    » ただし、デフォルトのまま何も考えずにリリースしてはダメ
  11. 11.
    iCloud バックアップ
    » Documents フォルダは iCloud バックアップの対象
    » iCloud バックアップの必要容量が膨れ上がってしまう
    » 気づかずにリリースしてしまうと一大事
  12. 12.
    対処方法
    » 以下のどれかで対処する
    » (1) ファイルの置き場所を変える
    » (1-a) defaultRealm の設定を変える
    » (1-b) デフォルト以外の Realm を使う
    » (2) ファイルの置き場所はそのままで、バックアップ対象から除外
  13. 13.
    Realm.Configuration
    var config = Realm.Configuration()
    config.fileURL = ...
    // (1-a) defaultRealm の設定を変える
    Realm.Configuration.defaultConfiguration = config
    // (1-b) デフォルト以外の Realm を使う
    let realm = try! Realm(configuration: config)
  14. 14.
    バックアップ対象から除外
    let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
    let realmURLs = [ // 注意:対象ファイルは Realm のバージョンにより変わる
    realmURL,
    realmURL.appendingPathExtension("lock"),
    realmURL.appendingPathExtension("note"),
    realmURL.appendingPathExtension("management")
    ]
    // (2) バックアップ対象から除外(注意:先に Realm を一度作成しておくこと)
    realmURLs.forEach { fileURL in
    fileURL.setResourceValue(true, forKey: NSURLIsExcludedFromBackupKey)
    }
  15. 15.
    どうすべき?
    » 基本的にはファイルの置き場所を変えるのが良い
    » どこに置くかはそのアプリの設計次第
    » バックアップ対象から除外する方法は非常手段
  16. 16.
    トランザクションを
    まとめる
  17. 17.
    トランザクション
    » データの書き込みは realm.write を使う
    » realm.write のたびにトランザクションが発生
    » (このあたり、Realm がデータベースであることを再認識)
  18. 18.
    トランザクションはまとめるべき
    » ファイルアクセスのため、オーバーヘッドがある
    » また、Realm ファイルが肥大化する原因にもなる
    » トランザクションログが溜まっていくため
    » Realm ファイル容量がすぐ大きくなるなら疑ってみる
  19. 19.
    トランザクションが増えるパターン(1)
    » ループの中で毎回 realm.write を呼んでいないか?
    » Reactive 系で realm.write の回数を意識しているか?
    » 小さな do や map で細かく realm.write していたりとか
  20. 20.
    トランザクションが増えるパターン(2)
    » Realm にラッパーを被せている場合(他 DB との互換性を考慮な
    ど)
    » 細かい変更のたびに realm.write を呼んでいないか?
  21. 21.
    どうすべき?
    » realm.write の回数を意識して減らす
    » Realm のラッパーはそもそも作らないのがベター
  22. 22.
    コンパクション
  23. 23.
    ファイル肥大化の対策
    » ファイルサイズが大きくなるのが気になる場合もある
    » 対策
    » トランザクションをまとめる(前述)
    » Realm ファイルを最適化する(コンパクション)
  24. 24.
    コンパクションの方法
    » realm.writeCopy(toFile:) で行える
    » 他の箇所で Realm を使っていないタイミングで行う
  25. 25.
    コンパクションの処理
    autoreleasepool {
    let realm = try! Realm()
    try! realm.writeCopy(toFile: tempURL)
    try! FileManager.default.removeItem(at: realmURL)
    try! FileManager.default.moveItem(at: tempURL, to: realmURL)
    }
  26. 26.
    どうすべき?
    » コンパクションは必須ではない
    » 気になるならアプリ起動時など Realm を使っていない箇所で実行
  27. 27.
    オブジェクトの受け渡し
  28. 28.
    Realm オブジェクトの受け渡し
    » メソッドの間の受け渡し
    » Model、ViewModel、View の間の受け渡し
    » スレッドの間の受け渡し
    » スレッドが異なる場合はそのまま渡すことはできない
  29. 29.
    受け渡す方法
    » オブジェクトをそのまま渡す
    » struct に変換して渡す
    » プライマリキーを渡す
    » ThreadSafeReference を使う
  30. 30.
    オブジェクトをそのまま渡す
    » 同じスレッドであることが明らかである場合
    » ただ、あまり階層をまたがって渡しすぎると分かりにくくなる
  31. 31.
    struct に変換して渡す
    » Realm オブジェクトではないものに変換する
    » 変換の手間がかかる
    » オブジェクトをそのままの形でなく色々手を加えたい場合にはあり
  32. 32.
    プライマリキーを渡す
    » スレッドをまたがる場合の手軽な方法のひとつ
    // 受け取る側
    let realm = try! Realm()
    let person = realm.object(ofType: Person.self,
    forPrimaryKey: key)
  33. 33.
    ThreadSafeReference を使う
    » 新しい方法、プライマリキーがなくても良い
    // 渡す側
    let personRef = ThreadSafeReference(to: person)
    // 受け取る側
    let realm = try! Realm()
    let person = realm.resolve(personRef)
  34. 34.
    補足 : Realm インスタンスの取得
    » let realm = try! Realm() は必要な時にその都度実行して良い
    » 各メソッドで行う
    » 各クロージャで行う
    » コストがかかる処理ではない
    » realm を共有して使おうとするとかえって面倒
  35. 35.
    どうすべき?
    » プライマリキーまたは ThreadSafeReference が良い
    » オブジェクトそのものを渡すのは範囲を限定したい
    » struct に変換するのは、その変換自体に意味がある場合のみ
  36. 36.
    クエリをどこで行うか
  37. 37.
    クエリをどの階層で行うか
    » どの階層の責務か?
    » Model
    » ViewModel
    » View (ViewController)
  38. 38.
    Model
    » Model 層にビジネスロジックを持たせる
    » ここでクエリが行われる
    » ビジネスロジックを ViewModel や View に置かない
  39. 39.
    ViewModel
    » ViewModel 層は画面ごとの表示用コンテンツを作る
    » ここでもクエリを一部活用する
    » Model が変わった時に Live Object の機能利用
    » View は与えられたコンテンツをレイアウトするだけ
  40. 40.
    どうすべき?
    » Model : ビジネスロジックを持つ
    » ViewModel : 表示用コンテンツを作る
    » View : レイアウトのみ
  41. 41.
    まとめ
  42. 42.
    まとめ
    » ファイルの置き場所
    » トランザクションをまとめる
    » コンパクション
    » オブジェクトの受け渡し
    » クエリをどこで行うか