2

弱い参照とブロックについてはかなり理解できたと思っていましたが、以下のコード スニペットを試してみると、いくつか理解できないことがありました。

メソッドtest1 : すべて問題なく、オブジェクトは保持されません

メソッドtest2 : オブジェクトがメソッドtest3の最後まで保持されるように見える理由がわかりません! object = nilメソッドtest2の最後に明示的に設定しても何も変わりません。

メソッドtest3 : オブジェクトは保持されません。メソッドtest2がこのように動作しないのはなぜですか?

副次的な質問として、弱い変数がスレッドセーフかどうか実際に疑問に思っていましたか? つまり、異なるスレッドから弱い変数にアクセスしようとしたときに BAD_ACCESS 例外が発生しない場合です。

@interface Object : NSObject
@property (nonatomic) NSInteger index;
@end

@implementation Object

- (id)initWithIndex:(NSInteger) index {
    if (self = [super init]) {
        _index = index;
    }
    return self;
}

- (void)dealloc {
    NSLog(@"Deallocating object %d", _index);
}

@end

試験方法

- (void) test1 {
    NSLog(@"test1");
    Object* object = [[Object alloc] initWithIndex:1];
    NSLog(@"Object: %@", object);
    __weak Object* weakObject = object;
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{
        //NSLog(@"Weak object: %@", weakObject);
        [NSThread sleepForTimeInterval:2];
        NSLog(@"Exiting dispatch");
    });
    [NSThread sleepForTimeInterval:1];
    NSLog(@"Exiting method");
}

- (void) test2 {
    NSLog(@"test2");
    Object* object = [[Object alloc] initWithIndex:2];
    NSLog(@"Object: %@", object);
    __weak Object* weakObject = object;
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{
        NSLog(@"Weak object: %@", weakObject);
        [NSThread sleepForTimeInterval:2];
        NSLog(@"Exiting dispatch");
    });
    [NSThread sleepForTimeInterval:1];
    NSLog(@"Exiting method");
}

- (void) test3 {
    NSLog(@"test3");
    Object* object = [[Object alloc] initWithIndex:3];
    NSLog(@"Object: %@", object);
    NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{
        NSLog(@"Weak object: %@", [weakObject nonretainedObjectValue]);
        [NSThread sleepForTimeInterval:2];
        NSLog(@"Exiting dispatch");
    });
    [NSThread sleepForTimeInterval:1];
    NSLog(@"Exiting method");
}

- (void) test {
    [self test1];
    [NSThread sleepForTimeInterval:3];
    [self test2];
    [NSThread sleepForTimeInterval:3];
    [self test3];
}

上記の出力は次のとおりです。

2013-05-11 19:09:56.753 test[1628:c07] test1
2013-05-11 19:09:56.754 test[1628:c07] Object: <Object: 0x7565940>
2013-05-11 19:09:57.755 test[1628:c07] Exiting method
2013-05-11 19:09:57.756 test[1628:c07] Deallocating object 1
2013-05-11 19:09:58.759 test[1628:1503] Exiting dispatch
2013-05-11 19:10:00.758 test[1628:c07] test2
2013-05-11 19:10:00.758 test[1628:c07] Object: <Object: 0x71c8260>
2013-05-11 19:10:00.759 test[1628:1503] Weak object: <Object: 0x71c8260>
2013-05-11 19:10:01.760 test[1628:c07] Exiting method
2013-05-11 19:10:02.760 test[1628:1503] Exiting dispatch
2013-05-11 19:10:04.761 test[1628:c07] test3
2013-05-11 19:10:04.762 test[1628:c07] Object: <Object: 0x71825f0>
2013-05-11 19:10:04.763 test[1628:1503] Weak object: <Object: 0x71825f0>
2013-05-11 19:10:05.764 test[1628:c07] Exiting method
2013-05-11 19:10:05.764 test[1628:c07] Deallocating object 3
2013-05-11 19:10:05.767 test[1628:c07] Deallocating object 2
2013-05-11 19:10:06.764 test[1628:1503] Exiting dispatch
4

1 に答える 1

1

あなたの質問のいくつかに触れる前に、あなたの 3 つのテストについて 2 つの所見があります。

  1. 3 つのテストすべてを連続して実行していて、実行ループに戻らず、自動解放プールがフラッシュされていないため、テストが複雑になっています (そのため、テストよりも長く持続しているように見えます)。通常はそうします)。何が起こっているのかを本当に理解するために、一度に 1 つずつテストを行う必要があります。オブジェクトの寿命について結論を出すのは良くありませんが、自動解放プールがフラッシュされないようにしていないという事実のアーティファクトが実際に発生している可能性があります。

  2. これらすべてのテストを as として実行していますdispatch_async。これにより、ディスパッチされたブロックが非常に迅速に開始されます。場合によっては、基になるオブジェクトが範囲外になるよりも速く開始さweakObjectれ、ディスパッチされたブロックの最初のステップの 1 つとして にアクセスすることがよくあります。を使用することをお勧めしdispatch_afterます (つまり、呼び出し元のメソッドに変数をスコープ外にする機会を実際に与えることになります)。これにより、何が起こっているのかがよくわかります。

あなたのテストは良いデータポイントですが、 を使用して同じものをdispatch_afterテストし、それらの数を減らして一度に 1 つのテストを行うと便利だと思いますsleepForTimeInterval。あなたのテストの特異性のいくつかは、いくつかの重要な動作を偽造しているように感じます.

とにかくあなたは尋ねます:

メソッド test2: オブジェクトがメソッド test3 の最後まで保持されるように見える理由がわかりません! メソッド test2 の最後で object = nil を明示的に設定しても、何も変わりません。

これは間違いなく自動解放プールに分類され、testメソッドが完了するまで排出されません。

前のポイントに、もう一度test2やり直してみてください。ただし、操作を にアクセスする前に 2 秒待機させますweakObject(または、これらのsleepForTimeIntervalステートメントをすべて削除して、のdispatch_after代わりに使用しますdispatch_sync)。

- (void) test2 {
    NSLog(@"test2");
    Object* object = [[Object alloc] initWithIndex:2];
    NSLog(@"Object: %@", object);
    __weak Object* weakObject = object;
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{
        [NSThread sleepForTimeInterval:2];      // new sleep
        NSLog(@"Weak object: %@", weakObject);
        [NSThread sleepForTimeInterval:2];
        NSLog(@"Exiting dispatch");
    });
    // [NSThread sleepForTimeInterval:1];       // not really necessary
    NSLog(@"Exiting method");
}

これが期待どおりに動作することがわかります。

メソッド test3: オブジェクトは保持されません。メソッド test2 がこのように動作しないのはなぜですか?

言うまでもなく、あなたtest3は非常に悪いニュースであり、簡単にクラッシュします。たとえば、sleep 行をコメントアウトしてみてください。

- (void) test3 {
    NSLog(@"test3");
    Object* object = [[Object alloc] initWithIndex:3];
    NSLog(@"Object: %@", object);
    NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{
        NSLog(@"Weak object: %@", [weakObject nonretainedObjectValue]);
        [NSThread sleepForTimeInterval:2];
        NSLog(@"Exiting dispatch");
    });
//    [NSThread sleepForTimeInterval:1];
    NSLog(@"Exiting method");
}

のように振る舞うのではなく、 のように振る舞っているように思えweakますunsafe_unretained

副次的な質問として、弱い変数がスレッドセーフかどうか実際に疑問に思っていましたか? つまり、異なるスレッドから弱い変数にアクセスしようとしたときに BAD_ACCESS 例外が発生しない場合です。

多くの方法で例外を取得できます。weakObjectそうでないことを要求するメソッドnil(例: NSMutableArraymethod ) に渡すとaddObject、例外が発生します。nilオブジェクト ポインターの ivar を逆参照する場合にも、例外が発生する可能性がありますobj->objectIvar。たとえば、弱い参照を使用して を保持しないようにしますが、ivar を逆参照できるようにローカルの強い参照を持つObjectインスタンス メソッドを想像してください。doSomethingLaterObject

- (void)doSomethingLater
{
    __weak Object *weakSelf = self;

    double delayInSeconds = 10.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        Object *strongSelf = weakSelf;
        NSLog(@"%d", strongSelf->_index); // **BAD** - this can crash of `self` has been released by this point
    });
}

したがって、通常は上記を次のように置き換えます。

- (void)doSomethingLater
{
    __weak Object *weakSelf = self;

    double delayInSeconds = 10.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        Object *strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"%d", strongSelf->_index);
        }
    });
}

ただし、正直に言うと、最初のコード サンプルがクラッシュする可能性があり、2 番目のコード サンプルがクラッシュしない理由の詳細は、非同期プログラミングでオブジェクト参照を慎重に使用することが重要であり、状況を慎重に処理できないという明白な事実ほど重要ではありません。例外が発生する可能性があります。weakObject多くの場合、 ではないことを確認することで、nilこの種の問題の多くを防ぐことができます (いくつかの注意点については説明しません)。これは、オブジェクト メソッドを呼び出すときはあまり重要ではありませんが (メッセージを に送信すると が返されるためnil) nil、 yourweakObjectがパラメーターであるか、ivar に対して逆参照されている場合は重要です。

しかし、はっきりさせておきたいのですが、どれも実際にはスレッドセーフとは何の関係もありません。ロック メカニズムなどの同期を適切に処理するか、キュー(シリアル キュー、またはdispatch_barrier_async書き込みdispatch_sync用と読み取り用の同時キューのリーダー/ライター パターン) を適切に使用することで、スレッド セーフを実現します。

例外が発生しないようにオブジェクト参照を慎重に処理しているコードがあるからといって、スレッドセーフを達成したとは限りません。スレッド セーフに伴う別の懸念事項があります。

于 2013-05-11T21:05:02.160 に答える