RealmとObjectMapperの組み合わせで Cannot invoke initializer for type ‘User’ with an argument list of type ‘(forPrimaryKey: String)

投稿者: Anonymous

RealmとObjectMapper(https://github.com/Hearst-DD/ObjectMapper)を組み合わせで以下のようなモデルを作っているのですが、このモデルを User(forPrimaryKey: “1”) の呼び出し方でインスタンス化しようとした場合、Cannot invoke initializer for type ‘User’ with an argument list of type ‘(forPrimaryKey: String)’ と怒られてしまいます。

class User: RLMObject, Mappable {

    var id:String?
    var name:String?

    required init?(_ map: Map) {
        super.init()
        mapping(map)
    }

    func mapping(map: Map) {
        id <- map["id"]
        name <- map["name"]
    }

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

おそらく、RLMObjectクラスとMappableプロトコルの多重継承が原因だとおもうのですが、なぜMappableプロトコルを継承すると、User(forPrimaryKey: “1”) が利用できなくなるのが理解できておりません。
RealmというよりはSwiftの言語利用の話になると思うのですが、ご存知の方、お手数ですがご教授いただけると助かります。

Mappableプロトコルの内容は以下です。

public protocol Mappable {
    init?(_ map: Map)
    mutating func mapping(map: Map)
}

再現コードを以下に置きます。
https://github.com/okitsutakatomo/RealmWithObjectMapper

解決

MappableプロトコルのイニシャライザがRLMObjectで定義されているobjectForPrimaryKey:からSwiftにマッピングされたイニシャライザを隠してしまうようなので、少し無理やりですが、下記のようにエクステンションで別名のメソッドを定義して呼び出すのはいかがでしょうか?

extension RLMObject {

    class func objectInRealm(realm: RLMRealm, forIdentifier: AnyObject) -> RLMObject? {
        return self(inRealm: realm, forPrimaryKey: forIdentifier);
    }

    class func objectForIdentifier(identifier: AnyObject) -> RLMObject? {
        return self(forPrimaryKey: identifier)
    }
}

また、試す過程でObjectMapperがJSONからオブジェクトを生成するときに呼び出すinitで正しくRLMObjectクラスのinitが呼ばれなかったのを確認しました。
そのため、おそらく下記のようにinitをオーバーライドする必要があります。

また、Realmで使用するモデルのクラス(RLMObjectのサブクラス)のプロパティはすべてdynamic修飾子を付加する必要があります。そうしないと、値を正しく取り出すことができません。

また、プライマリキーの型がオプショナルだと、ObjectMapperがJSONからオブジェクトを生成する際にプライマリキーがnilという例外で失敗したので、定義を直して初期値を与えるようにしました。

class User: RLMObject, Mappable {
    dynamic var id: String = ""
    dynamic var username: String = ""

    // For Realm
    override init!() {
        super.init()
    }

    override init!(objectSchema schema: RLMObjectSchema!) {
        super.init(objectSchema: schema)
    }

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

    // For ObjectMapper
    required convenience init?(_ map: Map) {
        self.init()
        mapping(map)
    }

    func mapping(map: Map) {
        id          <- map["id"]
        username    <- map["username"]
    }
}

上記の修正したコードについては、次のコードで動作を確認しました。

let json = "{"id": "1", "name": "katsumi"}"
let user = Mapper<User>().map(json)

println("user: (user?.id, user?.name)")

let realm = RLMRealm.defaultRealm()

realm.transactionWithBlock { () -> Void in
    realm.addOrUpdateObject(user)
}

let results = User.objectsInRealm(realm, "id = %@", "1")
println("results: (results)")

if let u = User.objectForIdentifier("1") as? User {
    println("user: (u)")
} else {
    println("user: null")
}
回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *