カッパでも分かるiOSアプリゲーム開発

カッパがひたすらゲーム制作に関することを書くブログです。Railsに関するTipsもたまにまとめてます。

Realm にはスレッドセーフな primary key がサポートされてなかった

スポンサードリンク

ほんきで学ぶSwift+iOSアプリ開発入門 Swift2,Xcode7,iOS9対応

f:id:InvokeTwoA:20151023183618p:plain「かっぱクエストの開発もついに佳境」
f:id:InvokeTwoA:20151215172640p:plain「ゴクリ……(いつも佳境とかクライマックスとか言ってるなぁ)」
f:id:InvokeTwoA:20151023183618p:plain「そして開発の最終段階で判明する問題。それがマルチスレッド問題!」
f:id:InvokeTwoA:20151215172640p:plain「ま、まさか……」
f:id:InvokeTwoA:20151023183618p:plain「realm のプリマリーキー重複エラーが頻繁に起きてしまったぜ」

swift + realm でプライマリーキーを実装してみたが……

class HogeModel: Object {
    
    dynamic var id = 1      // primary id

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

    static func lastId() -> Int {
        let realm = try! Realm()
        if let party = realm.objects(HogeModel.self).last {
            return party.id + 1
        } else {
            return 1
        }
    }
}

f:id:InvokeTwoA:20151023183618p:plain「こんなサンプルがネットにはゴロゴロ落ちていた」
f:id:InvokeTwoA:20151215172640p:plain「ふむふむ」
f:id:InvokeTwoA:20151023183618p:plain「データ登録のタイミングが重ならないようなアプリならこれで問題ないんだけど、同時に更新しまくるようなアプリだと下記のようなエラーが出ちゃったよ」

Terminating app due to uncaught exception 'RLMException', reason: 'Can't create object with existing primary key value '6'.'


f:id:InvokeTwoA:20151215172640p:plain「プライマリーキーが重複していて登録できないエラー!!」
f:id:InvokeTwoA:20151023183618p:plain「うむ。同時にデータ更新したら lastId で取れる値が重複しちゃってエラーになるんだよね」

公式の見解を見てみた

公式の見解を見てみた

f:id:InvokeTwoA:20151023183618p:plain「なぜ公式ドキュメントを真っ先に参照しなかったのかぁ!」
f:id:InvokeTwoA:20151215172640p:plain「か〜な〜し〜み〜の〜」

  • 公式ドキュメント

realm.io

  • 公式のコメント

Realm doesn’t have auto-incrementing properties

Realm doesn’t have a mechanism for thread-/process-safe auto-incrementing properties commonly used in other databases when generating primary keys. However, in most situations where a unique auto-generated value is desired, it isn’t necessary to have sequential, contiguous, integer IDs.

In these cases, a unique string primary key is typically sufficient. A common pattern is to set the default property value to NSUUID().UUIDString to generate unique string IDs.

Another common motivation for auto-incrementing properties is to preserve order of insertion. In some situations, this can be accomplished by appending objects to a List or by using a createdAt property with a default value of NSDate()

  • 3行でまとめると

Realm はオートインクリメントを持ってないよ。
Realm はオートインクリメントの仕組みを利用したとしても、スレッドセーフなオートインクリメントに対応してないよ。

もし良ければユニークな文字列でプライマリーキーを実装してみたら?
NSUUID().UUIDString 使えばいけるいける。

或いは createdAt プロパティとかを用意すればできんじゃないの?

f:id:InvokeTwoA:20151215172640p:plain「うへぇ。スレッドセーフなのはサポートしてないよと明言されてるぅ!」
f:id:InvokeTwoA:20151023183618p:plain「こいつはまいった。まいったゾウ」

対策

f:id:InvokeTwoA:20151023183618p:plain「最新順にデータを表示するときに id を desc で表示するのが便利だったけど諦めるかー」
f:id:InvokeTwoA:20151215172640p:plain「素直に createdAt を作って、それを降順で表示するようにしましょう」
f:id:InvokeTwoA:20151023183618p:plain「プライマリーIDはなんやかんやであると便利なので、 UUIDString 使った文字列で実装するかー」

class HogeModel: Object {
    
   dynamic var id = 1      // primary id
   dynamic var createdAt = NSDate()

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

    // メソッド名変えた方が良い。というかメソッドの必要性がもう無い
    static func lastId() -> Int {
            return   NSUUID().UUIDString
    }

  // 最新順にデータ取得。primary_idでのソートは無理なので createdAtでソート
    static func recent() -> Results<HogeModel> {
        let realm = try! Realm()
        let hoges = realm.objects(HogeModel.self).sorted(byProperty: "createdAt", ascending: true)
        return hoges
    }
}

f:id:InvokeTwoA:20151023183618p:plain「ふーっ。マルチスレッドな処理は想定外な事が沢山起きて大変だー」
f:id:InvokeTwoA:20151215172640p:plain「もう技術的に難しいところは殆ど終わったので、そろそろカッパクエストも開発完了するはず……」

  • かっぱクエストがもはやアプリではなく概念となってきていてやばい
  • しかしこの困難の先にこそ光があるはずなのだ……