LoginSignup
5
1

More than 5 years have passed since last update.

Realm移行時にStringly typedなAPIを回避する

Posted at

iOS開発者にとって、Realmはそれまで敷居の高かったCore Dataと格闘するコストを削減してくれるDBライブラリです。

今やRealmはMobile Platformの導入により、iOS / Androidアプリ開発におけるDBのライブラリの代表格となっています。

Swift全般にも言えますが、Realmを使ってうれしいことは、strongly typed apiなので、
アクセスにおけるtypoが発生しないことです。

ですが、Objective-Cから存在するStringly TypedなAPIを使用する場面は発生してしまいます。僕が実際に遭遇したユースケースとその対処法を紹介します。

APIから取得したデータをRealmに保存する

あるAPIによって、jsonをレスポンスとして得たとき、そのレスポンスをそのままRealmに保存するとします。

Dictionaryにシリアライズして保存してもいいのですが、Stringly Typedになってしまうので、Himotokiなどの型安全なJSONデコーダをつかって、Realmオブジェクトにマッピングします。

変換したJSONを、RealmObjectのサブクラスに生やした下記のようなメソッドでRealmObjectに変換します。

class Sample:Object {

    func convert(from response: APIResponse.Hoge) {
        let realm = try! Realm()
        do {
                hoge = response.hoge
                fuga = response.fuga

            try realm.write {
                realm.add(self, update: true)
            }
        } catch {

        }
    }
}

Himotokiのdecodeでこれをやらないのは、常にRealmに保存するとは限らないからです。

これはみなさんもよく遭遇するユースケースだと思います。

DBとして用いられているUserDefaultsをRealmに移行する

UserDefaultsにDictionaryを入れて保存している場合がありますが、内部に一体何が保存されているのかわからないので、こういうものはとっととRealmにマイグレーションします。

上記と同じ例でいけそうです。

class Sample:Object {

    let hoge = RealmOptional<Int>()
    let fuga: String? = nil

    func converted(from data: [String : Any]) {
        let realm = try! Realm()
        do {
                hoge.value = data["hoge"]
                fuga = data["fuga"]

            try realm.write {
                realm.add(self, update: true)
            }
        } catch {

        }
    }
}

上記のユースケースなら遭遇する例も多く、また実装も難しいものではありません。RealmObject側をOptionalにしていますが、これはケースバイケースになると思います。

実際にあった怖い話:保存しているキーのTypo

これは実際にあった怖い話ですが、UserDefaultsに配列が保存されており、その内部がDictionaryになっているデータがありました。

こんなやつ

[
    { "images" : 
        [ 
            { "name" : "name" },
            { "name" : "name" }
        ]
    }
]

しかし、保存するメソッドが統一されておらず、キーのtypoによって、実際にはこんな感じになってました

[
    { "images" : //imagesが正しい
        [ 
            { "name" : "name" },
            { "name" : "name" }
        ]
    },
    { "image" ://typo!! 
        [ 
            { "name" : "name" },
            { "name" : "name" }
        ]
    }
]

このままではマイグレーションできません。マイグレーションする前に、キーは統一しておかなくてはなりません。

Dictionaryをdecodeする時に下記のような処理を通さざるを得ませんでした。Himotokiを使っています。

static func convert(from dictionary: [String : Any]){

    let images: [Image]? = {
        do {
            let images: [Image] = try e <|| "images"
            return images
        } catch {
            do {
                let images: [Image] = try e <|| "image"
                return images
            } catch {
                return nil
            }
        }
    }()
}

このような事故が起こらないように、日頃からStrongly TypedなAPIを使うように心がけたいですね。

結論

他にも古いプロジェクトでは、SQLiteから移行する場合等もあると思いますが、基本的には、RealmObjectのサブクラスにマッピング用のメソッドを生やすのがよさそうです。

Stringly Typed APIは良くないので、Strongly TypedなAPIになるように作り変えていきましょう。

参考資料

今回の内容はtrySwift!NYCでAndyy Hopeさんが発表されていたお話をRealm周辺で適応するとどうなるかという話でした。僭越ながら、Realmでこの講演の日本語への翻訳を担当させていただきましたので、ぜひこちらもお読みください。

Swift Eye for the Stringly Typed API(日本語)

5
1
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
1