web-dev-qa-db-ja.com

dispatch_async関数で弱い自己を使用する

__weak self内でdispatch_asyncを使用することに関する多くの投稿を読みましたが、今は少し混乱しています。

私が持っている場合:

self.myQueue = dispatch_queue_create("com.biview.core_data", NULL);

dispatch_async(self.myQueue, ^(void){
    if (!self.var1) {
        self.var1 = ...;
    }
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        if ([self.var2 superview]) {
            [self.var2 removeFromSuperview];
        }

        [self.Label setText:text];
    });
});

__weak selfを使用する必要がありますか。場合によってはdispatch_asyncを読んだので、__weak selfは必要ありません。

最後のコメントはこちら

50
Marko Zadravec

selfUIViewControllerへのオブジェクトポインターであると仮定します。

考慮事項:

  • UIViewControllerは「UIKit」オブジェクトです。 UIKitオブジェクトは、非メインスレッドのメソッドに送信されません。つまり、これらのメソッドはメインスレッドでのみ実行する必要があります。

  • キューに入れられたブロック-これが同期的であるか非同期的であるかにかかわらず、最終的には実行済み-何であれ!まあ、これが起こる前にプログラムが終了しない限り。

  • キャプチャーされた保持可能strongポインターはretainedブロックがコピーされるとき(たとえば、非同期にディスパッチされるとき)、再びreleasedブロックがされるとき(終了後)破棄されます。

  • キャプチャーされた保持可能weakポインターは保持されず、解放されません。

メインキューでディスパッチされるブロックでselfをキャプチャするシナリオでは、悪いことが起こることを心配する必要はありません。

なぜ?そして実際に何が起こるのでしょうか?

selfcapturedディスパッチされるブロックでasynchronouslyになるので、selfは暗黙的にretained、およびreleasedブロックが終了したら再び。

つまり、selfの有効期間は、ブロックが終了するまでextendedになります。 secondブロックがメインスレッドでディスパッチされ、そのブロックが実行されたときにselfがまだ生きていることが保証されていることに注意してください。

上記の「寿命の延長」は、プログラムの望ましい機能である可能性があります。

explicitlyUIViewControllerオブジェクトの寿命を延長したくなく、代わりにブロックが必要な場合-最終的に実行されるとき-checkかどうかこのUIViewControllerオブジェクトはまだ存在しているので、selfの__weakポインターを使用できます。 UIViewControllerがまだ生きているか、その間に割り当て解除されたかに関係なく、ブロックは最終的に実行されることに注意してください。

UIViewControllerの割り当てが解除された場合、ブロックは「何もしない」ことを望むかもしれませんbeforeブロックが実行されます:

MyController* __weak weakSelf = self;
dispatch_async(queue, ^{
    MyController* strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
    else {
       // self has been deallocated in the meantime.
    }
});

参照: ARCリリースノートへの移行

要確認:UIKitオブジェクトは、非メインスレッドのメソッドに送信されません。

UIKitオブジェクトがメインスレッドでのみメソッドを実行するという事実により、もう1つの微妙なエラーが発生する場合があります。

ブロックが非同期にディスパッチされるUIKitオブジェクトをキャプチャし、non-mainスレッドで実行される場合、これは違反される可能性があります。その後、ブロックがそのUIKitオブジェクトへのlast強い参照を保持することが起こります。これで、最終的にブロックが実行されると、ブロックが破棄され、UIKitオブジェクトが解放されます。これが最後のstrongUIKitオブジェクトへの参照であるため、割り当てが解除されます。ただし、これはブロックが実行されたスレッドで発生します。これはメインスレッドではありません。 deallocメソッドは依然としてUIKitオブジェクトに送信されるメソッドであるため、今では悪いことが起こります(通常は起こります)。

UIKitオブジェクトへの強力なポインターをキャプチャするブロックをディスパッチし、ダミーメソッドを送信することで、このエラーを回避できます。

UIViewController* strongUIKitPointer = ... 
dispatch_async(non_main_queue, ^{
    ... // do something 
    dispatch(dispatch_get_main_queue(), ^{
        [strongUIKitPointer self];  // note: self is a method, too - doing nothing
    });
});

ただし、シナリオでは、last strong参照は、メインスレッドで実行されるブロック内にのみ存在する可能性があります。したがって、この微妙なエラーから安全です。 ;)

編集:

セットアップでは、保持サイクルはありません。保持可能なオブジェクトAが別の保持可能なオブジェクトBを強く参照し、オブジェクトBがAを強く参照する場合、保持サイクルが発生します。「ブロック」も保持可能なオブジェクトであることに注意してください。

循環参照を使用した不自然な例:

typedef void(^my_completion_block_t)(NSArray* result);

@interface UsersViewController : UIViewController
@property (nonatomic, copy) my_completion_block_t completion;
@property (nonatomic) NSArray* users;
@end

ここには、値の型がブロックであるプロパティ完了があります。つまり、タイプがブロックの_completionという名前のivarを取得します。

クライアントは、特定の操作が終了したときに呼び出される完了ハンドラーを設定できます。操作がリモートサーバーからユーザーのリストを取得するとします。操作が終了したら、プロパティsersを設定する計画です。

不注意なアプローチでは、誤って循環参照が導入されます。

「UsersViewController.m」のどこかに

self.completion = ^(NSArray* users){
    self.users = users;
}

[self fetchUsers];  // start asynchronous task

ここで、selfは、ブロックであるivar _completionへの強い参照を保持します。また、ブロック自体はselfをキャプチャします。これにより、ブロックがディスパッチされたときにコピーされるときにselfが保持されます。これは古典的な参照サイクルです。

その循環参照を回避するために、いくつかの選択肢があります。

  1. self__weak修飾ポインターの使用

    UsersViewController* __weak weakSelf = self;
    self.completion = ^(NSArray* users) {
        UsersViewController* strongSelf = weakSelf;
        if (strongSelf) {
            strongSelf.users = users;
        }
        else {
            // the view controller does not exist anymore
        }
    }   
    [usersViewController fetchUsers];
    
  2. self__block修飾ポインタを使用し、最終的にブロック内でnilに設定すると、終了します。

    UsersViewController* __block blockSelf = self;
    self.completion = ^(NSArray* users) {
        blockSelf.users = users;
        blockSelf = nil;
    }   
    [usersViewController fetchUsers];
    

参照: ARCリリースノートへの移行

132
CouchDeveloper

迅速な更新:

Swiftのこのいわゆる弱弱ダンスの例:

Swift 4.2:

func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let self = self else { return }
            self.updateView()
        }
    }
}

スウィフト3および4:

func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let strongSelf = self else { return }
            strongSelf.updateView()
        }
    }
}

スイフト2:

func doSomeThingAsynchronously() {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> () in
        // Do task in default queue
        dispatch_async(dispatch_get_main_queue(), { [weak self] () -> () in
            guard let strongSelf = self else { return }
            // Do task in main queue
            strongSelf.updateView()
        })
    }
}

人気のあるオープンソースプロジェクトAlamofireはこのアプローチを使用しています。

[weak self]およびguard letstrongSelf =を使用してオブジェクトの寿命を延長self else {return}イディオム。

詳細については Swift-style-guide をご覧ください

27