LoginSignup
22
22

More than 5 years have passed since last update.

Realm の createOrUpdateInRealm:withObject: の挙動調査

Last updated at Posted at 2015-02-25

先日の「Realm Tech Talk with JP Simard #2」で質問した挙動のまとめです。

Swiftで作っているアプリで、REST APIのレスポンスをキャッシュしてローカルでDBとして使いたくなって、でもCore Dataも大袈裟でめんどくさいなぁと思っていました。そこで出会ったのが、今話題のRealmです。ドキュメントにある「REST APIs」をみて「これだ!」と思いました。

createOrUpdateInRealm:withObject:を使うと、入れ子になったJSONをリレーションを含めDBのレコードにしてくれるようです。以下、その挙動調査です。

環境

バージョン
Realm 0.90.6
Xcode 6.1.1
Deployment Target iOS 8以上

Realmはバージョンが0.01上がっただけで大幅に機能が変わったりするらしいので要注意です → http://www.slideshare.net/ryugoo/impression-of-realm-java

検証用モデル

検証用にプライマリキーを持つCompany、プライマリキーを持たないLocation、プライマリキーを持ちCompanyとLocationとリレーションがあるPersonの3つのモデルを定義しました。

class Company: RLMObject {
    dynamic var id = 0
    dynamic var name = ""
    dynamic var phone = ""

    override class func primaryKey() -> String! {
        return "id"
    }
}

class Location: RLMObject {
    dynamic var latitude = 0.0
    dynamic var longitude = 0.0
}

class Person: RLMObject {
    dynamic var id = 0
    dynamic var name = ""
    dynamic var company: Company?
    dynamic var location: Location?

    override class func primaryKey() -> String! {
        return "id"
    }
}

検証

どんな挙動をするか、どんな使い方ができるかを調べるために、現実的ではない検証もあります。モバイルアプリ向けのAPIだと1画面1APIなことが多くて、そういう場合は一部の属性が返ってこなかったり、リレーション先まで含めて一回で取得したりするので、そんなケースも想定したりしています。

単純な追加

まずは単純にCompanyの追加をやってみます。withObject:に指定しているDictionayがJSONをデジリアライズした結果だと思ってください。Appleには電話番号がありますが、Mercari, Inc.は電話番号を公開していないのでレスポンスに含まれていません。

let realm = RLMRealm.defaultRealm()

realm.beginWriteTransaction()
Company.createOrUpdateInRealm(realm, withObject: ["id": 0, "name": "Apple", "phone": "1-800-MY-APPLE"])
Company.createOrUpdateInRealm(realm, withObject: ["id": 1, "name": "Mercari, Inc."])
realm.commitWriteTransaction()

println(Company.allObjectsInRealm(realm))

結果

RLMResults <0x7ff48a4d2450> (
    [0] Company {
        id = 0;
        name = Apple;
        phone = 1-800-MY-APPLE;
    },
    [1] Company {
        id = 1;
        name = Mercari, Inc.;
        phone = ;
    }
)

Companyが2つ作られました。Mercari, Inc.の電話番号はphoneを指定していないのでデフォルト値の空文字列になっています。

単純な更新

Appleの名前がApple Inc.に変わったので、それを受け取ってCompanyを更新してみます。ついでにレスポンスにphoneを含めないようにしてみました。

realm.beginWriteTransaction()
Company.createOrUpdateInRealm(realm, withObject: ["id": 0, "name": "Apple Inc."])
realm.commitWriteTransaction()

println(Company.allObjectsInRealm(realm))

結果

RLMResults <0x7fd8f05d6d00> (
    [0] Company {
        id = 0;
        name = Apple Inc.;
        phone = 1-800-MY-APPLE;
    },
    [1] Company {
        id = 1;
        name = Mercari, Inc.;
        phone = ;
    }
)

nameが更新されました。一方phoneはそのままです。なるほど、SQLでいうUPDATE文みたいな動きをするようです。

入れ子になったオブジェクトの追加・更新

Personを追加して、同時にCompanyやLocationを追加・更新してみます。1つ目はすべての情報がそろったレスポンス、2つ目はCompanyの情報が一部ないレスポンス、3つ目はCompanyもLocationもまったく含まれていないレスポンスからの追加です。

realm.beginWriteTransaction()
Person.createOrUpdateInRealm(realm, withObject: ["id": 0, "name": "Bill Gates", "company": ["id": 2, "name": "Microsoft"], "location": ["latitude": 100, "longitude": 100]])
Person.createOrUpdateInRealm(realm, withObject: ["id": 1, "name": "Steve Jobs", "company": ["id": 0, "name": "Apple Inc."]])
Person.createOrUpdateInRealm(realm, withObject: ["id": 2, "name": "Shintaro Yamada"])
realm.commitWriteTransaction()

println(Person.allObjectsInRealm(realm))
println(Company.allObjectsInRealm(realm))
println(Location.allObjectsInRealm(realm))

結果

RLMResults <0x7fa879d49790> (
    [0] Person {
        id = 0;
        name = Bill Gates;
        company = Company {
        id = 2;
        name = Microsoft;
        phone = ;
    };
        location = Location {
        latitude = 100;
        longitude = 100;
    };
    },
    [1] Person {
        id = 1;
        name = Steve Jobs;
        company = Company {
        id = 0;
        name = Apple Inc.;
        phone = ;
    };
        location = (null);
    },
    [2] Person {
        id = 2;
        name = Shintaro Yamada;
        company = (null);
        location = (null);
    }
)
RLMResults <0x7fa879d4b4f0> (
    [0] Company {
        id = 0;
        name = Apple Inc.;
        phone = ;
    },
    [1] Company {
        id = 1;
        name = Mercari, Inc.;
        phone = ;
    },
    [2] Company {
        id = 2;
        name = Microsoft;
        phone = ;
    }
)
RLMResults <0x7fa879d4c080> (
    [0] Location {
        latitude = 100;
        longitude = 100;
    }
)

結果をみると、Bill Gatesの追加にともないCompanyやLocationもちゃんと追加されていますが、プライマリキーを持つCompanyのオブジェクトであるApple Inc.phoneが空文字列になってしまいました。nameに別の値を指定した場合だと、nameは指定通り更新されますが、やはりphoneは空文字列になってしまいます。これはSQLでいうREPLACE文みたいな動きですね。

うむー、createOrUpdateInRealm:withObject:のドキュメントには入れ子になったオブジェクトがプライマリキーを持っている場合は、同じようにcreateOrUpdateInRealm:withObject:が呼ばれると書いてありますが、そうではないのでしょうか…?

プライマリキーを持たない子オブジェクトの更新

id = 0であるBill Gateslocationを更新してみます。

realm.beginWriteTransaction()
Person.createOrUpdateInRealm(realm, withObject: ["id": 0, "location": ["latitude": 1234, "longitude": 1234]])
realm.commitWriteTransaction()

println(Person.objectsInRealm(realm, "id = 0"))
println(Location.allObjectsInRealm(realm))

結果

RLMResults <0x7f89a8d4a4c0> (
    [0] Person {
        id = 0;
        name = Bill Gates;
        company = Company {
        id = 2;
        name = Microsoft;
        phone = ;
    };
        location = Location {
        latitude = 1234;
        longitude = 1234;
    };
    }
)
RLMResults <0x7f89a8d4bd00> (
    [0] Location {
        latitude = 100;
        longitude = 100;
    },
    [1] Location {
        latitude = 1234;
        longitude = 1234;
    }
)

この結果は予想できたのですが、以前のLocationが残ったままになっています。こういうのは自動で削除されるとうれしいですね。

感想

これらの結果から、createOrUpdateInRealm:withObject:を入れ子になったJSONに使うのは、この挙動を考慮したJSONになっていない場合は色々面倒な気がします。マスター的なデータや初期データをさくっとDB化するのには便利そうです。

UPDATE文でもREPLACE文でもどちらの挙動でもよいのですが、子オブジェクトに対しても同じ挙動をしてくれるとまだ使いやすいのになぁと思いました。

まあでも、JSONのキーはスネークケース、Objective-CやSwiftのプロパティはキャメルケースであることが多いと思うので、現実的にはcreateOrUpdateInRealm:withObject:は使わずに、プロパティひとつずつセットしていくことになる気もします。

22
22
2

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
22
22