iOS 開発: __weak __strong はブロックのネストを解決します

Block循環参照を使用すると問題が発生し、Block複数のネストされた使用状況はさらに複雑になり、nilオブジェクトにも問題が発生します。

なぜ循環参照があるのでしょうか?

現在、iOS 開発はARC引用计数管理モードになっています。別の記事「ブロックの基本原理」を参照してください。Block外部変数へのアクセスは、一時変数の場合は値のコピー (深いコピー)、__block変更された変数の場合はポインタのコピー (浅いコピー) であることがわかっています。変数のスコープは制限されており、block実行後に解放されるため、どちらの場合もこの説明の範囲外です。オブジェクトBlockタイプにアクセスする場合 (例: Dog类)、Block外部変数にアクセスすると__strongオブジェクトが変更されます (NSObjectデフォルトは__strong変更されます)。 ) は、実行されたオブジェクト自体の関数copy(浅いコピー) です。

  1. Q: インスタンス オブジェクトへのアクセスをブロックすると、循環参照が発生しますか?
- (void)testBlock {
    
    
    void(^block1)(void) = ^{
    
    
        self.name = @"张三";
    };
    block1();
}

答え: いいえ。を出力することでself.nameプルーフは、キャプチャされたオブジェクトが浅いコピーであり、参照カウントが +1 であり、関数呼び出しが完了すると解放され、ペアの参照カウントが -1 であることを再度証明するnameように変更されています。したがって、循環参照は存在しません。@"张三"blocktestBlockblock1self

  1. 循環参照という状況は、
    実際にはオブジェクトが永久に保持され、解放できないdeallocため呼び出されず、メモリオーバーフローが発生することを意味しており、これが私たちが懸念している問題です。この問題の根本的な原因は、オブジェクトが保持されているものの解放されておらず、参照カウントが常に 0 より大きいことです。
    たとえば、A は B を保持し、B も A を保持し、誰もメモリを解放できません。これが最も一般的です。
- (void)testBlock {
    
    
    self.block1 = ^{
    
    
        self.name = @"张三";
    };
    block1();
}
//self持有了block1,block1又访问了self,

また、A が B を保持し、B が C を保持し、C が D を保持し...D が A を保持することもあります。最終的には閉ループを形成する必要があります。

- (void)testBlock {
    
    
	
    self.block1 = ^{
    
    
       void(^block2)(void) = ^{
    
    
           self.name = @"张三";
       }; 
	   block2();
    };
    block1();
}
//self持有了block1,block1持有了block2访问了self,block2访问了self。

私たちがしなければならないのは、強制参照によって形成された連鎖を断ち切ることです。
画像の説明を追加してください

__弱い原則

循環参照を解決するには、変数を変更し__weakて。では、__weakなぜそれができるのでしょうか?

ランタイムは、オブジェクトへのすべてのポインターを格納するweak_table_t ハッシュテーブルを維持します。はポイントされたオブジェクトのアドレス、ポインターのアドレス (このアドレスの値はポイントされたオブジェクトのポインター アドレスです) 配列です。weakKeyValueweak

 __weak typeof(self) weakSelf = self;
  • 弱い変数を初期化すると、関数runtimeが呼び出されます。NSObject.mmobjc_initWeak
id objc_initWeak(id *location, id newObj)
{
    
    
    if (!newObj) {
    
    
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
  • *location: __weakweakSelf のポインタアドレス
  • newObj: 参照されるオブジェクト、つまりこの例ではself
  • objc_initWeakstoreWeakこの関数は、主にポインターを更新し、対応する弱参照テーブルを作成する関数を呼び出します。
template <HaveOld haveOld, HaveNew haveNew,
          enum CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj)
{
    
    
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

 retry:
    // 如果weak指针变量 之前弱引用过一个对象 讲这个对象对应的SideTable从SideTables中取出来,赋值给oldTable
    if (haveOld) {
    
    
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
    
    
        // 如果weak指针变量 之前没有弱引用过一个obj,则oldTable = nil
        oldTable = nil;
    }
    
    //  如果weak指针变量要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
    if (haveNew) {
    
    
        newTable = &SideTables()[newObj];
    } else {
    
    
        // 如果weak指针变量不需要引用一个新obj,则newTable = nil
        newTable = nil;
    }
    
    // 加锁操作,防止多线程中竞争冲突
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
    
    
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
    
    
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&
            !((objc_class *)cls)->isInitialized()) //  如果cls还没有初始化,先初始化,再尝试设置weak
        {
    
    
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry; // 重新获取一遍newObj,这时的newObj应该已经初始化过了
        }
    }
    
    
    
    // 如果weak指针变量之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址

    if (haveOld) {
    
    
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    
    // 如果weak指针变量需要弱引用新的对象newObj
    if (haveNew) {
    
    
        
        // 1 调用weak_register_no_lock方法,weak_register_no_lock会将weak指针变量的地址 记录到newObj对应的weak_entry_t中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);

        // 2 更新newObj的isa的weakly_referenced bit标志位
        if (!newObj->isTaggedPointerOrNil()) {
    
    
            newObj->setWeaklyReferenced_nolock();
        }
        
        // 3 *location 赋值,也就是将weak指针变量直接指向了newObj   这里并没有将newObj的引用计数+1 , 所以weak引用不会让newObj引用计数+1
        *location = (id)newObj;  // 也就是例子中 将weakSelf 指向self
    }
    else {
    
    
        // No new value. The storage is not changed.
    }
    
    // 解锁,其他线程可以访问oldTable, newTable了
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

.
    // 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}
  • オブジェクトの参照カウントが 0 の場合、基になる_objc_rootDeallocメソッドが呼び出されてオブジェクトを解放します。
  • _objc_rootDealloc-> object_dispose-> objc_destructInstance-> clearDeallocating-> clearDeallocating_slow->weak_clear_no_lock
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    
    
    objc_object *referent = (objc_object *)referent_id;
    
    // 找到referent在weak_table中对应的weak_entry_t
     weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); 
        if (entry == nil) {
    
    
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    // 找出weak引用referent的weak指针地址数组以及数组长度
    if (entry->out_of_line()) {
    
    
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
    
    
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
    
    
        objc_object **referrer = referrers[i]; // 取出每个weak 指针变量的地址
        if (referrer) {
    
    
            if (*referrer == referent) {
    
     // 如果weak 指针变量确实weak引用了referent,则将weak指针变量设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
                *referrer = nil;
            }
            else if (*referrer) {
    
     // 如果所存储的weak 指针变量没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry); // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}

弱い、強い、ダンス

ブロックに遅延関数を埋め込み、オブジェクト メモリがクリアされたことを出力に表示します。

- (void)checkBlock {
    
    
    Dog *dog = [Dog new];
    __weak Dog *dog_weak = dog;
    dog.eatBlock = ^(NSString * _Nonnull name) {
    
    
        dog_weak.name = name;
        dispatch_async(dispatch_get_main_queue(), ^{
    
    
            dog_weak.name = @"牛肉";
            NSLog(@"wuwuFQ:%@", dog_weak);//输出 wuwuFQ:(null)
        });
        
    };
    dog.eatBlock(@"鸡肉");
    NSLog(@"wuwuFQ:%@", dog.name);//输出 wuwuFQ:鸡肉
}

dispatch_asyncこれはメインスレッドによって非同期に実行され、checkBlock関数の実行後に実行される可能性があり、その前にオブジェクトがdog解放されるため、弱参照dog_weakポインタが生成されますnilこの問題を解決するために次のように使用します
__strong

- (void)checkBlock {
    
    
    Dog *dog = [Dog new];
    __weak Dog *dog_weak = dog;
    dog.eatBlock = ^(NSString * _Nonnull name) {
    
    
        dog_weak.name = name;
        __strong Dog *dog_strong = dog_weak;
        dispatch_async(dispatch_get_main_queue(), ^{
    
    
            dog_strong.name = @"牛肉";
            NSLog(@"wuwuFQ:%@", dog_strong);//输出 wuwuFQ:<Dog: 0x6000027ddce0>
        });
        
    };
    dog.eatBlock(@"鸡肉");
    NSLog(@"wuwuFQ:%@", dog.name);//输出 wuwuFQ:鸡肉
}

このコードをもう一度理解しましょう。ここでは、メインスレッドがタスクを非同期に実行し、そのタスクがの最後追加dispatch_asyncされ、オブジェクトが解放されます。ポインターへの強い参照を使用することで、変数を内部的にキャプチャできるようになります実行が完了すると、変数は解放され、循環参照は生成されません。main_queue()checkBlockdispatch_asynccheckBlockdog
__strongdog_weakdispatch_asyncdog_strongdispatch_asyncdog_strong

おすすめ

転載: blog.csdn.net/wujakf/article/details/129182902