Realm objectの取り回し方法について考える

（なぜか限定公開でPublicにしてなかったので今頃公開。基本的な考え方は今でも通じるはず）

# なぜ考える必要があるのか

- 保存されたRealm object（Managed Realm object）は[スレッドを直接またげなかったり](https://realm.io/docs/swift/latest/#models)、[変更にはtransactionが必要](https://realm.io/docs/swift/latest/#writes)だったりと気をつけることが多いです。
- そのため、運用ルール以外の方法で安全にRealm objectを取り回す方法を考える必要があります。

# [VO](https://qiita.com/sagaraya/items/96708cd451021fb040b7#vo-value-object)を何にするか

Realm object([DAO](https://qiita.com/sagaraya/items/96708cd451021fb040b7#dao-data-access-object))を[VO](https://qiita.com/sagaraya/items/96708cd451021fb040b7#vo-value-object)に変換すれば安全に取り回すことができるので、[VO](https://qiita.com/sagaraya/items/96708cd451021fb040b7#vo-value-object)をどうするかを考えます。

## 1. struct

**定義例**

```swift
protocol PersonType {
    var name: String { get }
    var age: Int { get }
}

@objcMembers
class Person: Object, PersonType {
    dynamic var name = ""
    dynamic var age = 0
}

struct PersonObject: PersonType {
    let name: String
    let age: Int
}
```

- Realm objectとstructの相互変換する方法が必要
- Realm objectとstructで同一の定義が必要で冗長

## 2. Unmanaged Realm objectをprotocolで返す

**定義例**

```swift
protocol PersonType {
    var name: String { get }
    var age: Int { get }
}

@objcMembers
class Person: Object, PersonType {
    dynamic var name = ""
    dynamic var age = 0
}
```

- Managed Realm objectをUnmanaged Realm objectにする方法が必要
- setterを含むProtocolも返せば[DTO](https://qiita.com/sagaraya/items/96708cd451021fb040b7#dto-data-transfer-object)とすることも可能

# 相互変換の方法

| 方法 | デメリット |
| --- | --- |
| 変換コードを自前で書く | 変換コードのメンテナンスコストが高く、ミスが入る可能性も高い |
| Codable経由で変換 | Decode/Encode分の処理コスト増、Codableに準拠するためのコードが必要 |
| [Extra/Realm](https://github.com/smartnsoft/Extra)の[detached()](https://github.com/smartnsoft/Extra/blob/master/Extra/Classes/Realm/DetatchableObject.swift#L35) | 実装はディープコピー。ただ、ユニットテストが書かれていないので不安 |
| ~~[EVReflectable/Realm](https://github.com/evermeer/EVReflection)経由で変換~~ | [fetchしたオブジェクトは正常にリフレクションできなかったので除外](https://github.com/evermeer/EVReflection/issues/227) |
| [EasyRealm](https://github.com/PoissonBallon/EasyRealm)の[unmanaged()](https://github.com/PoissonBallon/EasyRealm/blob/master/EasyRealm/Classes/Variable.swift#L24) | 実装はディープコピー。RealmSwiftのバージョンが[EasyRealm.podspec](https://github.com/PoissonBallon/EasyRealm/blob/master/EasyRealm.podspec#L37)に依存してしまう |

## 有力候補

### [EasyRealm](https://github.com/PoissonBallon/EasyRealm)

- [ユニットテスト](https://github.com/PoissonBallon/EasyRealm/blob/master/Example/Tests/TestVariable.swift)あり。

```swift
let trainer = Trainer()
let pokedex = Pokedex()
trainer.pokemons.append(HelpPokemon.generateCapturedRandomPokemon())
trainer.pokedex = pokedex

trainer.pokemons.forEach {
  $0.specialBoost.value = 42
}
    
try! trainer.er.save(update: true)
let managed = trainer.er.managed!
XCTAssertTrue(managed.er.isManaged)
XCTAssertTrue(managed.pokedex!.er.isManaged)
XCTAssertFalse(managed.pokemons.isEmpty)
managed.pokemons.forEach {
  XCTAssertTrue($0.er.isManaged)
    XCTAssertEqual($0.specialBoost.value!, 42)
}

let unmanaged = managed.er.unmanaged
XCTAssertFalse(unmanaged.er.isManaged)
XCTAssertFalse(unmanaged.pokedex!.er.isManaged)
XCTAssertFalse(unmanaged.pokemons.isEmpty)
unmanaged.pokemons.forEach {
  XCTAssertFalse($0.er.isManaged)
  XCTAssertEqual($0.specialBoost.value!, 42)
}
```
# まとめ

- 公式がサポートするまで何らかの代替方法で乗り切るしかありません。
    - ちなみにrealm-javaには[copyFromRealm()](https://stackoverflow.com/a/38637134)があります。
- どの方法も変換コストがかかるのでパフォーマンスとのトレードオフになります。パフォーマンスを求めたい部分はRealm objectやResults等を直接扱って気をつけて運用するしかないのかなと思います。