CoreDataに大量のデータを追加するとき、メモリに気をつけてあげないとすぐメモリ不足になってしまいます。 しかし、CoreDataのManagedObjectは解放したいと思ったタイミングではなかなか解放してくれません。 メモリ解放するにはちょっとした手順が必要となります。
- 新しくManagedObjectContextを作る
- ManagedObjectをひとつ追加→保存する
- ManagedObjectContextのresetを呼ぶ(メモリ強制開放)
- 2〜3を必要なだけ繰り返す
- 他のManagedObjectContextで作ったManagedObjectに保存結果をマージする
ちなみに、大量のデータを変更・処理する時なども手順は同じです。
新しくManagedObjectContextを作る
まず、新しくManagedObjectContextが必要になります。 なぜ新しいのが必要かというと、このcontextで作ったManagedObjectはステップ3のresetで強制的にすべて解放されてしまうからです。 いろんな所でManagedObjectの保持をしていた場合、それらすべてがexe bad accessにってしまいます。 そこで、ManagedObjectContextを分けることにより、最低限のManagedObjectのみ解放するようにします。 ちなみに、persistentStoreCoordinatorはすべてのManagedObjectContextで共通で大丈夫です。
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init]; [moc setPersistentStoreCoordinator:self.persistentStoreCoordinator];
ManagedObjectをひとつ追加→保存する
この手順は普通のManagedObjectContextを使う時とほぼ変わりません。 保存をするのは、メモリ解放したときにsaveしていないデータが飛んでしまうからです。
Book \*book = [NSEntityDescription insertNewObjectForEntityForName:@"Book" inManagedObjectContext:moc]; book.title = @"本のタイトル"; NSError \*error = nil; [moc save:&error]; if(error){ NSLog(@"moc error : %@",error); }
ManagedObjectContextのresetを呼ぶ(メモリ強制開放)
強制的にManagedObjectを解放します。ここで保存していないデータは消去され、さらにretainされているManagedObjectは後々Exe Bad Accessになります。
[moc reset];
2〜3を必要なだけ繰り返す
必要なだけ繰り返してください。
他のManagedObjectContextで作ったManagedObjectに保存結果をマージする
新しく作ったManagedObjectContextでは新規データが保存されていますが、ほかのContextではその結果を知らないままになっています。 このまま放おっておくと、ほかのContextでsaveをしたときに、エラーになる可能性があります (SVNで言うところのコンフリクト状態になる) なので、予めマージして上げます。 すこしこのマージが曲者で、一気にマージできずにManagedObject一つ一つマージしないといけません。
[[self.managedObjectContext] refreshObject:managedObject mergeChanges:YES];
ここでデータ全部を取ってくるなどをするとそこでメモリ不足になってしまうので、必要な分だけを見極めてマージしてください。 どうしてもどの範囲をマージしていいかわからない場合は、最終手段としてすでに取得済みのManagedObjectすべてをマージするという手法もあります。
for( NSManagedObject \*obj in [self.managedObjectContext registeredObjects]){ [self.managedObjectContext refreshObject:obj mergeChanges:YES]; } for( NSManagedObject \*obj in [self.managedObjectContext updatedObjects]){ [self.managedObjectContext refreshObject:obj mergeChanges:YES]; } for( NSManagedObject \*obj in [self.managedObjectContext insertedObjects]){ [self.managedObjectContext refreshObject:obj mergeChanges:YES]; } for( NSManagedObject \*obj in [self.managedObjectContext deletedObjects]){ [self.managedObjectContext refreshObject:obj mergeChanges:YES]; }
ただ、重くなる上に確実でもないのできちんと個別にやってあげたほうがいいと思います。