スレッド間で NSManagedObjectContext を使い回すのはダメらしい

iPad アプリを作ってたら、エラーとともに、良くわからないメッセージ (T^T)

Not safe to look up objc runtime data.

試しに上記メッセージで検索すると、スレッドに関係するっぽい FAQ が見つかる。


エラーは NSManagedObjectContext save の実行タイミングで発生してるけど、毎回ってわけじゃない。しかも非同期通信やってて別スレッドが save を呼び出してるから、スレッドセーフじゃないってのは容易に推測できる。^^;)

そー思いながら、調べてたら、こんな記述を Apple Core Data Programming Guide で見つけた。

管理対象オブジェクトコンテキストをロックした場合(すなわち、正常に tryLock できた場合)は、ロック解除を呼び出すまで、ロックを発動したスレッドでコンテキストを保持きるようにする必要があります。マルチスレッド環境でコンテキストを適切に保持しないと、デッドロックが発生します。

管理対象オブジェクトの使用 メモリ管理


さらに検索してたら Core Data multi thread applicationApple Document に注意書きあるよーって記述みつけた。思い切り、スレッド間で NSManagedObjectContext を使い回す実装してました。すんません。 ^^;)>

Basic rules are:

1. Use one NSPersistentStoreCoordinator per program. You don't need them per thread.
2. Create one NSManagedObjectContext per thread.
3. Never pass an NSManagedObject on a thread to the other thread.
4. Instead, get the object IDs via -objectID and pass it to the other thread.
 
More rules:

1. Make sure you save the object into the store before getting the object ID. Until saved, they're temporary, and you can't access them from another thread.
2. And beware of the merge policies if you make changes to the managed objects from more than one thread.
3. NSManagedObjectContext's -mergeChangesFromContextDidSaveNotification: is helpful.

Core Data multi thread application


どーやって実装すれば良いかなーと思ってたら、こちらが参考になりそう。


直接メッセージには触れてないけど、NSManagedObjectContext はスレッド毎に管理しましょーとなっているところを見ると、同じようなケースにハマったんだろね。

参考にさせてもらいまする :-)

追記 9/22

他にもこんなの見つけた。stack over flow にあった Q&A で、ぼくの考えた方法(1)でどうよ?というのに対して(2)の方がいいんじゃね?というアドバイスが飛んでる。まだ良く読めてないけど、こちらも参考になりそうなのでメモっとこ。:-)

NSError コードの調べ方

NSFileManager の -moveItemAtPath:toPath:error: でファイルを移動しようとしたら、こんなエラーが出た。大抵の場合、エラーメッセージから推測できるんだけど、これは不親切。(T^T)

The operation couldn’t be completed. (Cocoa error 512.)


仕方ないので、NSError コードの調べ方をググってみたら、こちらさんの記事が分かりやすかった。参考にさせていただきます :-)


基本は Error Handling Programming GuideのError Objects, Domains, and Codes セクションを見ましょーとのこと。

Mac OS X(やiOS)のエラーは、エラードメインに分けられていて、NSError.hに次の4つのエラードメインが定義されています。ふむふむ。φ(.. )


エラーは4つのドメインに分かれてて、

  • NSMachErrorDomain
  • NSPOSIXErrorDomain
  • NSOSStatusErrorDomain
  • NSCocoaErrorDomain


さらにフレームワーク毎に定義されたエラードメインがあって、各ドメインのエラー番号は次のヘッダファイルに定義されてるそうな。Xcode が入ってる Mac で Spotlight 検索すると、見つけやすいとのこと。ふむふむ φ(.. )

Cocoa (NSCocoaErrorDomain)
WebKitErrorDomain
NSURLErrorDomain
NSXMLParserErrorDomain
Carbon (NSOSStatusErrorDomain) CarbonCore.framework/Headers/MacErrors.h
POSIX (NSPOSIXErrorDomain) /usr/include/sys/errno.h
Mach (NSMachErrorDomain) /usr/include/mach/kern_return.h


調べ方の基本は分かった。さっそく試してみよ。:-)

今回のは NSFileManager のエラーだから、Foundation/FoundationErrors.h で良いはず。Spotlight で検索すると FoundationErrors.h が見つかった。

/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/FoundationErrors.h

Xcode で開いてみると、、、おーい ^^;)

enum {
....
NSFileWriteUnknownError = 512, // Write error (reason unknown)