iOS で View 用に Entity を Struct に変換するってそれ Protocol で出来ることではないんでしょうか

  • 38
    Like
  • 10
    Comment

この資料は何か?

  • iOS/Androidの勉強会での発表資料です
  • 間違えもあるだろうという部分を修正していきたいので
    • コメント欄にご意見貰えればそれを反映していきたいです

言いたいこと 3つ

  • 要件的にStructに変換する必要があればもちろんやらざるを得ない
  • ただ、Structに変換する際にRealm.ResultやPHFetchResultをループさせて取り出すのはコストが高いよね
  • 単にImmutableにしたいならそれProtocolで出来ませんかね

はなしの導入

他の勉強会で、10,000件のRealm.ObjectからStructに変換したら80秒かかったので短縮したとか、しなかったとか


自分の知っている永続化などのフレームワークについて


そもそもRealmについて

  • Realmはlazy loadされる
    • 永続化されたプロパティはアクセスした際にデータが読み込まれる
  • Realmはmemory mappedを利用する
    • メモリの読み書きが直接ファイルの読み書きになり高速
  • 取得結果Realm.ResultにはArray的なインターフェースでアクセスできる

lazy loadについて

  • 例えばRealm.Resultが10,000件取得した結果になっても個別のインスタンスのプロパティにアクセスするまでメモリに読み込まれてない
  • これは一覧を表示するtableView/collectionView に特に効果的
    • 表示されるときにメモリに読み込まれる
    • 表示されないものはメモリに読み込む必要がない

Realm.Resultからループで取り出す

  • 取得条件にlimitはないので条件にあったものを基本的には全て取得する
  • Realm.Resultに対してループ回してStruct用にアクセスすると、その場で全件がメモリに読み込まれる

なかなかこれでページングしたりするのは厳しい

今のところ、Realm.Resultはlazy loadするからページングする必要ないという割り切りで作られていて、全件Structに変換しなきゃいけないなんてことは使い方として苦肉の策でしょう。


比較としてPHFetchResultについて

ユーザのアルバムから写真を取ってくるやつ

  • PHFetchResultはdynamicaly load
  • 大量の結果を取得する場合も必要に応じて動的なロードで最適なパフォーマンス
  • これもArray的なインターフェースでアクセスする

PHFetchResultをループで取り出す

  • これもlimitはない
  • UIの要件としてcollectionViewのグルーピングを工夫するためにループで取り出し
    • [[String: [PHAsset]]] にする時はある
  • 基本的にはカメラロール、アルバム単位、各種スマートアルバム単位などのグループから取得するのでそれで済むならそのほうがいい

ちなみにCore Dataについて

  • limitがある!
  • tableView/collectionViewにはNSFetchResultControllerが最適になってる
  • 良くない点も結構あるけどそれはまた今度!

ここまでのまとめ

  • RealmやPHFetchResultはパフォーマンスを意識して設計されている
  • なぜパフォーマンスを気にしなければいけないか
    • Realmはモバイル端末上で動くアプリケーションのためだし
    • PHFetchResultは件数が計り知れないし
  • Core Dataはそこまでクセがない
    • 検討に値する

それでも俺達は取得したデータをView層ではImmutableにデータを扱いたいじゃん


そう、それだけが要点ならProtocolでいいじゃんよというのが本題です。


Viewに表示するために渡したオブジェクトを再度保存するのはご法度だという考えは正しい

  • もし、UI側で値を変更されるような作りなら、その値の変更から反映すべき

他のレイヤーで引き回したデータを再利用されたくないがパフォーマンスのために新しくStructを作りたくない

  • なので参照だけで済むようにImmutableなProtocolだけ作る

View層でEntityをProtocol経由でアクセスする


// 例えばView層で表示に使うProtocol
protocol SomeProtocol {
    var identifier: String { get }
    var imageURL: URL? { get }
}
// 永続化されてるEntity
class SomeEntity: Realm.Object { 
    dynamic var id
    /* もともとの様々な定義を省略 */
}
// Protocolに準拠するようにしておく
extension SomeEntity: SomeProtocol {
    var identifier: String { return id }
    var imageURL: URL? { return url }
}
  • Protocolに準拠する実装はextensionじゃなくてもいいけど分けたいので
  • 実装上そのままreturnしてるけど必要ならカスタマイズする

Arrayで利用するときはProtocolのArrayでいい

let array: [SomeProtocol] = ...

もちろんRxSwiftの文脈でも使える

Observable<SomeProtocol>

DEMO


  • こういう動くViewがある
  • 流れているのはTweetでWeb APIで取得してEntityにしたもの

スクリーンショット 2017-06-20 0.13.24(2) 2.png

  • 白い文字はWebAPIで取得したEntity
  • 赤いのはシステム通知として用意した別のEntity

2種類の別のEntityを一つのProtocolに準拠するようにして[SomeProtocol]をViewに渡してそれを流す


Q & A


Q: そもそもRealm.ObjectをStructにしたいときって何?

  • 分からないけどRealm.Objectだけの情報じゃ足りないことがあるのかも
  • 自分だったらマイグレーションしてなんとかしてるだけかもしれない

Q: レイヤーごとにデータを変えるという手法について聞いたことあります

  • モバイルアプリでなくモバイル用のフレームワークでもなかったらそういうのもありかも
  • 多人数で開発してViewのレイヤー担当が先に作っておくよというときに有効かも
    • ただそれもProtocolで充分な気もする

まとめ


  • よっぽどのことがない限りRealm.ObjectやPHAssetをStructにしないほうがいい
    • Protocolを検討する
  • 写真を並べるだけならPHFetchResultをArrayのインターフェースでアクセスするのがいい
    • 写真を扱うライブラリはPHFetchResultをこねくり回してないか利用する前に確認した方がいい

おしまい