Edited at

CoreDataでatomicにsaveする

More than 3 years have passed since last update.


Core Dataは関係データベースでも関係データベース管理システム(RDBMS)でもありません。 (by Apple CoreDataプログラミングガイド)


ORマッパーでもありませんし、永続ストアのひとつとしてたまたまSQLiteを使えるっていうだけで、プライマリーキー制約なんて概念もありません。

なので例えば

User

-uid
-name

こんなモデルがあったとして

[

{
"uid": 1,
"name": "kentaro"
},
{
"uid": 2,
"name": "yamada"
}
...
]

こんなjsonレスポンスを返すAPIがあった場合、

//APIアクセスしてレスポンス取得

NSNumber* uid = ..;
NSString* name = ...

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *ctx) {
User* user = [User MR_findFirstByAttribute:@"uid" withValue:uid inContext:ctx];
if (user == nil) {
user = [User MR_createInContext:ctx];
user.uid = uid;
}
person.name = name;
} completion:^(BOOL success, NSError *error) {
assert(success);
}];

こういうコードを書いても、とくにクラッシュすることなく普通にうごきます。

ただし、このようなAPIアクセス〜永続化処理が、並列で同時に100回くらい実行されると、、、

int cnt = [Person MR_countOfEntitiesWithPredicate:[NSPredicate predicateWithFormat:@"uid = 1"]];

NSLog(@"count = %d", cnt);

//> count:100

あれー、uid=1のレコードが複数保存されちゃってるー、ってことが起こりえます。

saveWithBlock:ブロック内部がアトミックじゃないからです。

こんな場合は、たとえばGCDの直列ディスパッチキューを一つ用意して、Test and Set を実行するブロックは必ずそのキュー上で実行させてみます。

static dispatch_queue_t serialQueue;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
serialQueue = dispatch_queue_create("net.kenmaz.serialqueue", DISPATCH_QUEUE_SERIAL);
});

dispatch_async(serialQueue, ^{
//ここから〜
NSManagedObjectContext* ctx = [NSManagedObjectContext MR_context];

User* user = [User MR_findFirstByAttribute:@"uid" withValue:uid inContext:ctx];
if (user == nil) {
user = [User MR_createInContext:ctx];
user.uid = uid;
}
person.name = name;

[ctx MR_saveToPersistentStoreAndWait];
//〜ここまで排他的に実行
});

100並列実行しても・・・

int cnt = [Person MR_countOfEntitiesWithPredicate:[NSPredicate predicateWithFormat:@"uid = 1"]];

NSLog(@"count = %d", cnt);

//> count:1

おkぽ

みなさん、このあたりはどのように書いてますかね?

よいアイデアがあればおせーてください。

ちなみにこのへん↓でも似たような話をしてるっぽい

http://stackoverflow.com/questions/7718801/is-a-gcd-dispatch-queue-enough-to-confine-a-core-data-context-to-a-single-thread