Help us understand the problem. What is going on with this article?

Realmのスレッドチェックの例外がObjective-Cの@throwからC++のthrowに変わってて戸惑った話

More than 3 years have passed since last update.

きっかけ

きっかけは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も残っているのでスレッドチェックだけ何らかの理由で変更されたようです。
yusuga
iOS Engineer.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away