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