【Swift】Realmでウィジェットとアプリ本体でデータを共有する

# １．はじめに
　データの永続化に`Realm` を利用して、アプリ本体とTodayExteinsion（以下、ウィジェット）とでそのデータを共有する方法を以下にまとめます。
　具体的には、アプリ本体で作成／更新したデータをウィジェットで表示したり、ウィジェットで更新したデータをアプリ本体で使用したりしたいと思います。
　ポイントは、以下の２つです。
　- `Embedded Framework` を使用して、ソースコードを共有する
　- `App Groupe` を使用して、データを共有する

# ２．ソースコードの共有
　データを共有したいので、`Realm` への保存や読み込みといったソースコードも当然共有したい。ターゲットごとに（アプリ本体とウィジェットそれぞれに）同一のソースコードを記述するのはムダ。
　ということで、ソースコードを共有するために`Embedded Framework` を作成します。

　1. Xcodeのツールバー "File" → "New" → "Target"、もしくはプロジェクト "General" から "Add a Target"
　2. "Cocoa Touch Framework" を選択
　3. フレームワーク名を入力し、"Finish"
　4. あとは共通化したい処理を`.swift` ファイルに記載するだけ


以下の記事が詳しかったです。
[Swift Embedded Framework の導入](https://qiita.com/UJIPOID/items/6b1a2e4a256942a0ebc1)

今回、`Embedded Framework` に `RealmManager` というクラスを作成しました。
当クラスの機能は以下の通りにしました。
　- シングルトンクラスとする
　- `Realm` へのオブジェクトの追加／削除／更新
　- `Realm` へ保存したデータの保持（アプリ側の表示に使用するデータソースとして）

```swift
protocol RealmManagerDelegate {
    func realmObjectDidChange()   // 後述②
}
class RealmManager {
    static let shared = RealmManager()
    var realmData = Array<RealmData>()
    var delegate: RealmManagerDelegate?
    
    private var realm: Realm!
    private var configuration: Realm.Configuration!  // 後述①
    private var token: NotificationToken?            // 後述②
    
    private init() {
        // 後述①
        configuration = Realm.Configuration()
        let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: App Groupe名を指定する)!
        configuration.fileURL = url.appendingPathComponent("db.realm")
        
        do {
            realm = try Realm(configuration: configuration)
            // Realmクラスの通知ハンドラを追加する。
            // 後述②
            token = realm.observe{ notification, realm in
                self.delegate?.realmObjectDidChange()
            }
        } catch {
            fatalError(String(describing: type(of: self)) + #function + "realm notification set error")
        }
        
        // 全件読み込んでrealmDataに設定
        // コードは省略
    }

    // データの取得、更新、削除等のコードは省略
}
```


# ３．データの共有
　まずは`AppGroup` を作成します。他のターゲットとデータを共有するために必要な設定です。

　1. ターゲットのCapabilitiesからAppGroupをONする
　2. 必要に応じて追加または既存のグループを選択

　通常、`Realm` でオブジェクトを保存する時は以下のように記述するかと思います。

```swift
do {
    let realm = try Realm()
    realm.add(/*保存するオブジェクト*/)
} catch {
    // エラー処理
}

```

　このように保存したデータは、データを保存したターゲットからしかアクセスできません。つまり、アプリ本体で保存したデータをウィジェット側からアクセスすることができないということです。
　そこで、以下のように記載します。`Realm` の引数がポイント。これでアプリ本体からもウィジェットからもアクセスできる場所に保存することができます。

```swift
configuration = Realm.Configuration()
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: App Groupe名を指定する)!
configuration.fileURL = url.appendingPathComponent("db.realm")
do {
    realm = try Realm(configuration: configuration)
// 以下略

```

# ４．データの更新の通知
　今回私が作ったアプリではアプリ本体からでもウィジェットからでも同一のデータを更新できる作りにしました。そこで、ウィジェットでデータを更新したのちにアプリ本体を起動したら、更新後のデータでUIを更新したいと考えました。
　当初は更新したい`ViewController` にオブザーバーを追加し、アプリがフォアグラウンドになった際にデータを再取得してUIを更新していました。
　しかし、毎回データを取得している点や、そもそも更新されていない場合もあり、ムダな処理に思ったので調べてみたら、`Realm` にはオブジェクトの更新を通知する機能がありました。この機能を使用して、オブジェクトの更新があった時だけUIを更新することができました。

```swift
// Realmクラスの通知ハンドラを追加する。
token = realm.observe{ notification, realm in
self.delegate?.realmObjectDidChange()

```

`self.delegate?.realmObjectDidChange()` は上記「２．ソースコードの共有」に載せたコードの後述②に該当するプロトコルを呼んでいます。

`ViewController` などを`RealmManager.delegate` に設定して通知を受け取ります。


# ５．まとめ
　上記のようにすることでデータとコードをアプリ本体とウィジェットで共有することができます。