LoginSignup
5
2

More than 3 years have passed since last update.

RealmSwiftの辛いところ

Last updated at Posted at 2018-09-30

ここ一年位RealmSwiftをつかって、感じたこと(ぐち)まとめ
辛い中でマシな方法を模索した知見も書いてあります

概要

RealmSwiftを使うと、Swiftの言語で本来できるはずの様々なことができなくなります。辛いです。できないことばかり。
RealmSwiftを使うということは、Swiftではなく、RealmSwiftという環境でコーディングすることだと思ったほうがいい。
ORMapperの都合が多すぎて、全然ドメインロジックに集中させてもらえない。

じゃあ、Realmに更にオレオレWrapperライブラリを被せればいいのかということになるが、

  • そんなことしている時点で、Realmを使う必要あるのか?
  • RealmのObservingを利用できない

などの疑問がある。

正直、普通にSqliteのORMappingを使うか、ORMappingを自分で書いたほうが良いと思う。
(最近GRDB.swiftというものを使ったが、本当に最高だった。)

私がRealmを使った唯一と言っていいRealmのストロングポイントは、Remoteとのpush型のsyncが便利なこと。ただ、ゆるふわーな要件でのみの話。厳密なノード間のトランザクションは存在しない。エンタープライズ向けとは思えない。(確か2018年中盤のころの知識なので今はなにかあるかもしれない)
(今ならFirestoreの方がイケてると思います!)

以下辛いところ

スレッドを超えられない

managedObjだとスレッドを超えられない。よって、managedかunmanagedかを常に意識しなくてはいけない。
managed->unmanaged変換も浅い変換しかサポートされていない。

私はHydraというswiftでasync/await的なコードを書けるライブラリを愛用しているが、managedObjをその中で利用しようものならその瞬間クラッシュする。awaitはシングルスレッドじゃなくて、マルチスレッドだけど待ってくれる(コルーチンというらしいです)だけだから。

どんなに辛くても通常のRealmObjectとは別にunmanagedObj用のstructを書いて区別して使うことを強く推奨したい。

モデル定義

モデル定義:デフォルト値なしのletを定義

できない。辛い。

モデル定義:自由にコンストラクタを作れない

モデル定義:デフォルト値なしのletを定義にも関連するけど、絶対初期値を入れて欲しいときに、

class Dog: Object {
  /// 空文字列と未定義の区別できない。開発者が常に脳みそのコストを払って注意すべし。
  @objc dynamic name = ""

  /// init()はnameが未定義に絶対使ってはならないというルールを開発者が常に脳みそのコストを払って徹底すること
  convenience init(name: String) {
    self.init()
    self.name = name
  }
}

辛い

モデル定義:インフラを意識した宣言が強制的に必要になる

@objc dynamic ←邪魔

モデル定義:自由にOptionalな基本型を定義できない

RealmOptional ←邪魔

モデル定義:非OptionalなObjectプロパティを定義できない

ValueObjectを扱えない(Structが扱えない)

RealmObjectをValueObjectとして利用しようとしたときの話です。
無理に扱おうとすると、きちんと責任を持ってOldObjectを削除しないとゴミだらけになる。というかそういうのを気にしなきゃいけないObjectはValueObjectではない。
やってられません。

なのでどんなに辛くてもRealmがサポートしているプリミティブ型(intとかstringとか)を流用して、決してRealmObjectクラスを作らないようにしなくてはなりません。

だめな例
class Human: Object {
  @objc dynamic var name: Name
}

// 本来はvalueObjectとして扱いたい
class Name: Object {
  @objc dynamic var first = ""
  @objc dynamic var last = ""
}

(私が思う)良い例
class Human: Object {
  @objc dynamic var first = ""
  @objc dynamic var last = ""

  var name: Name { return Name(first: first, last: last) }
}

struct Name {
  let first: String
  let last: String
}

Enumが扱えない

基本型のListが扱えない

  • なんでList<Int>ってやらせてくれないん?

継承がサポートされていない

正確に言うと、SuperClassでの検索がサポートされていない
以下の様に、移譲で継承関係を表現しつつ頑張るしか無い。

class Animal: Object {
  @objc dynamic isDead = true
  func die() {
    isDead = false
  }
}
class Dog: Object {
  var isDead: Bool { return super!.isDead }
  @objc dynamic super: Animal?
  func die () { super!.die() } // ← 残念なforcedUnwrap
  func しっぽをふる() {
  }
}

DownCastするはめになったら大変だ。TemplateMethodを実装しようと思うと大変だ。

ManagedObjectじゃないとObservingできない

いわゆるCRUDの画面で、RとUの画面の実装を統一しようとしたら詰む。
RealmのObservingはあくまで保存済みObjectのためのものらしい。
Updateの画面だと保存済みRealmObjに監視を効かせながら、textFieldの値を同期していればいい。でもCreateの画面だと、まだRealmに保存していないObjectを扱うことになり、そしてそれは監視できない。

ManagedObjectはwriteの中で、Unmanagedはwriteの外で変更しなくてはならない

↑ごめんなさい、久々に自分の記事を読み返して加湿修正しているのですが、書いた当時の私が何を言いたいか不明です

消しても子Objectが消えない

削除ロジックは自分で管理
ActiveRecordでいうdependency:destroyが恋しいですね。

RemoteとのTransactionはサポートされていない

RealmのtransactionはあくまでローカルのRealmFileへの書き込みに対してのみ。RealmFile同士のsyncにはTransactionもなにもない。
これでは、厳密な仕様を満たすことができない。書き込みDeviceを一つに絞るとか、書き込み用のサーバーを用意するとかしか思いつきません。
そして、何度も言っているように、そんなことをする羽目になっている時点でRealm使う意味あるのか?という疑問が生じます。

まとめ

ぐちばかりですが、逆に言うとこれらの辛みを認識して受け入れて、そして利用することで、Realmの様々な恩恵を受けることができます。
すべてを差し出した者にだけ、Realmの神様はホンモノの快適さを与えてくれるのです。

中途半端にフレームワークと喧嘩すると、双方の辛みを受けることになりかねないので、フレームワークとの喧嘩は慎重に…。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2