RealmモデルをiPhoneからApple Watchに送信する方法

# はじめに
RealmモデルをApple Watchに直接送信することはできないようです。
`Codable`属性を指定して、JSON等にエンコードすることで送信することができます。

エンコード方法については記事[^1]の補足になります。

ただし、Realmのバージョンが変わったことで若干仕様が変わっているようなので、本記事ではRealm ver.10.34.1現在での情報ということでご参考にしていただければと思います。

# エンコード方法
まずモデルを定義します。
```Swift
import Foundation
import RealmSwift

class Album: Object, Codable {
    @Persisted var id: String = UUID().uuidString
    @Persisted var name: String = ""
    @Persisted var content = RealmSwift.List<ImageInfo>()

    override public static func primaryKey() -> String? {
        return "id"
    }
    
    private enum CodingKeys: String, CodingKey {
        case id
        case name
        case content
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        try container.encode(content, forKey: .content)

    }
}
```
また
```Swift
import Foundation
import RealmSwift

class ImageInfo: Object, Codable {
    @Persisted var id: String = UUID().uuidString
    @Persisted var name: String = ""
    @Persisted var savePath: String = ""

    override public static func primaryKey() -> String? {
        return "id"
    }
    
    private enum CodingKeys: String, CodingKey {
        case id
        case name
        case savePath
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        try container.encode(savePath, forKey: .savePath)
    }
}
```
`Album`モデルのコンテンツとして`ImageInfo`を持っているモデルを定義してみました。

各モデルに`Codable`属性が指定されています。**両方のモデルに必要なことに注意してください。**
そうすることで、こういった入れ子構造のモデルもJSON化可能です。

では、実際にエンコードしていきます。
(今回は`realm.objects()`ですべてのオブジェクトを取得し、それぞれについてエンコードしてArrayに追加＆Array自体をさらにエンコードする、という形にしました)
```Swift
// 読み込み＆encode処理
let realm = try! Realm()
let albums: List = realm.objects(Album.self)

do{
    var dataArray: [Data] = []
    for album in albums {
            let albumData = try JSONEncoder().encode(album)
            dataArray.append(albumData)
    }
    let dataArrayEncoded = try JSONEncoder().encode(dataArray)
}catch{
    print(error)
}
```

Watch側でデコードしてRealmに保存するコードは以下です。

```Swift
// decode＆保存処理
do{

    let realm = try Realm()

    let dataArray = try JSONDecoder().decode([Data].self, from: dataArrayEncoded)

    for data in dataArray{
        
        let album = try JSONDecoder().decode(Album.self, from: data)
        
        try realm.write() {
            realm.add(album, update: .modified)  // データが無ければ追加、あったら上書き
        }
        
    }
    
}catch{
    print("decode error")
    print(error)
}
```

Watch側で保存したDBを覗くと以下のようになります。
```swift
let realm = try! Realm()
let albums = realm.objects(Album.self)
print(albums)
```
↓
```swift
[Album {
	id = 6342D5AD-339B-4876-8644-C6704A7A2ABF;
	name = All;
	content = List<ImageInfo> <0x6000020013f0> (
		[0] ImageInfo {
			id = 62C40A75-C52A-4CA4-BEA8-9A3D65FF8A33;
			name = image;
			savePath = file:///xxx.jpg;
		},
		[1] ImageInfo {
			id = 605AECA6-8CAD-4461-B7DD-F88F91EBA0D9;
			name = image;
			savePath = file:///xxx.jpg;
		},
		[2] ImageInfo {
			id = B31D5527-57B2-4F13-B1A8-7DB1A2FB068A;
			name = image;
			savePath = file:///xxx.jpg;
		}
	);
}, Album {
	id = AACD238E-3941-40E5-9DBB-C242532F488C;
	name = New;
	content = List<ImageInfo> <0x600002008c60> (
		[0] ImageInfo {
			id = 605AECA6-8CAD-4461-B7DD-F88F91EBA0D9;
			name = image;
			savePath = file:///xxx.jpg;
		},
		[1] ImageInfo {
			id = B31D5527-57B2-4F13-B1A8-7DB1A2FB068A;
			name = image;
			savePath = file:///xxx.jpg;
		}
	);
}]
```
エンコード処理を以下のようにしないのか？と思うかもしれません。
```Swift
let albumData = try Album.encode(to: JSONEncoder())
```
せっかく`Album`モデルで定義した`encode`を使っていないので「あれ？」となるのですが、上の例だと、以下のようなエラーになります。
```
argument type 'jsonencoder' does not conform to expected type 'encoder'
```
これにはJSONEncoder()の定義が起因しているようです。[^2] [^3]


# 送信方法
JSONエンコードすれば、`Data`型になるので、Watch Connectivityの`sendMessageData()`等で送ることができます。

# 参考文献
[^1]: https://qiita.com/cottpan/items/b75abd5d4e4ce73e00f2

[^2]: https://stackoverflow.com/questions/56366704/argument-type-does-not-conform-to-encodable

[^3]: https://kaitlin.dev/2019/01/07/jsonencoder-encoder.html