きっかけ
きっかけはRealmを0.96.3
から0.97.1
にあげた際、異常系の単体テストが通らなくなって気付きました。
だいたいこんな感じのテスト
Tweet *tweet = [Tweet objectForPrimaryKey:tweetID];
__weak Tweet *weakTweet = tweet;
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"%s", __func__]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
XCTAssertNotNil(weakTweet);
@try {
NSLog(@"text: %@", weakTweet.text);
}
@catch (NSException *exception) {
NSLog(@"%s; exception = %@;", __func__, exception);
[expectation fulfill];
}
@finally {
}
});
[self waitForExpectationsWithTimeout:5. handler:^(NSError *error) {
XCTAssertNil(error, @"error: %@", error);
}];
このテストは@try内でメインスレッドで生成したtweetにアクセスしているので
"Realm accessed from incorrect thread."
という例外が発生します。
なぜ例外が発生するかはRLMObjectはRLMObjectが参照するrealm
が生成されたスレッドとアクセスしたスレッドが同一であることが求められており、アクセス毎に内部でチェックされ異なる場合は例外を投げるようになっています。
コード
0.96.3
までは@throw
されています。realm-cocoa/Realm/RLMRealm_Private.hpp (38)
realm-cocoa/Realm/RLMRealm_Private.hpp
// throw an exception if the realm is being used from the wrong thread
static inline void RLMCheckThread(__unsafe_unretained RLMRealm *const realm) {
if (realm->_threadID != pthread_mach_thread_np(pthread_self())) {
@throw RLMException(@"Realm accessed from incorrect thread");
}
}
0.97.0
ではC++のthrow
に変わっています。realm-cocoa/Realm/ObjectStore/shared_realm.cpp (267)
void Realm::verify_thread() const
{
if (m_thread_id != std::this_thread::get_id()) {
throw IncorrectThreadException();
}
}
問題
これは何が問題かというとC++のthrow
は64bit環境だと@catch(id exception)
では例外を受け取ることできないからです。Exceptions in 64-Bit Executables
ドキュメントには@catch(...)
という引数を省略するという意味の...
を利用すれば良いとのことです(初めて見ましたが...
って可変引数以外でも使うんですね)。
修正したテスト
Tweet *tweet = [Tweet objectForPrimaryKey:tweetID];
__weak Tweet *weakTweet = tweet;
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"%s", __func__]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
XCTAssertNotNil(weakTweet);
@try {
NSLog(@"text: %@", weakTweet.text);
}
@catch (...) {
NSLog(@"%s, Error", __func__);
[expectation fulfill];
}
@finally {
}
});
[self waitForExpectationsWithTimeout:5. handler:^(NSError *error) {
XCTAssertNil(error, @"error: %@", error);
}];
まとめ
- Realmのスレッドチェックは
0.97.0
以降ではC++のthrow
が使われており、@catch(...)
を使う必要がある。 -
0.97.0
以降でも@throw
も残っているのでスレッドチェックだけ何らかの理由で変更されたようです。