iOS development: __weak __strong solves Block nesting

BlockThere will be problems with circular references when used, and Blockthe situation of multiple nested uses is more complicated, and there will also be nilproblems with objects.

Why is there a circular reference?

Now iOS development is in ARC引用计数management mode. Refer to another article "The underlying principle of Block". We know that Blockaccessing external variables is a value copy (deep copy) for temporary variables, and __blocka pointer copy (shallow copy) for modified variables. Both cases are outside the scope of this discussion, because the scope of the variable is limited and will be blockreleased after execution; if Blockthe object type is accessed (for example: Dog类), Blockaccessing the external variable will __strongmodify the object ( NSObjectthe default is __strongmodified ), is the function of the executed object itself copy(shallow copy).

  1. Q: Will Block accessing instance objects cause circular references?
- (void)testBlock {
    
    
    void(^block1)(void) = ^{
    
    
        self.name = @"张三";
    };
    block1();
}

Answer: No. By printing self.name, the proof namehas been modified to @"张三"prove again that blockthe captured object is a shallow copy, the reference count is +1, and when testBlockthe function call is completed, block1it is released, and selfthe reference count of the pair is -1, so there will be no circular reference.

  1. Circular reference situation
    actually means that the object will be held forever, and the object cannot be released, deallocso it will not be called, resulting in memory overflow. This is the problem we are worried about. The root cause of this problem is that the object is held but not released, and the reference count is always greater than 0.
    For example: A holds B, B also holds A, and no one can release the memory; this is the most common.
- (void)testBlock {
    
    
    self.block1 = ^{
    
    
        self.name = @"张三";
    };
    block1();
}
//self持有了block1,block1又访问了self,

There is also A holding B, B holding C, C holding D...D holding A. In the end, a closed loop must be formed.

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

What we have to do is break the chain formed by mandatory references.
Please add image description

__weak principle

To solve circular references, the most we can do is __weakto modify variables to break the chain of strong references. So __weakwhy can it be done?

Runtime maintains a weak_table_t hash table to store all weakpointers to an object. Keyis the address of the pointed object, Valueand is weakthe address of the pointer (the value of this address is the pointer address of the pointed object) array.

 __weak typeof(self) weakSelf = self;
  • When we initialize a weak variable, the function in runtimewill be calledNSObject.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: __weakpointer address of weakSelf
  • newObj: the object referenced, that is, in the exampleself
  • objc_initWeakThe function will call storeWeakthe function, which mainly updates the pointer and creates the corresponding weak reference table.
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;
}
  • When the reference count of the object is 0, the underlying _objc_rootDeallocmethod will be called to release the object.
  • _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
}

weak-strong-dance

We embed a delay function in the Block and print through the output that the object memory has been cleared.

- (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_asyncIt is executed asynchronously by the main thread. It may be checkBlockexecuted after the function is executed, and the object dogwill be released before, resulting in a weak reference dog_weakpointer nil.
We __stronguse to solve this problem:

- (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:鸡肉
}

Let's understand this code again. dispatch_asyncHere, the main thread executes the task asynchronously, which will be added to main_queue()the end of , which causes the object to be released. We use a strong reference to the pointer, which allows the variable to be captured internally . After the execution is completed, the variable is released and no circular reference is generated.checkBlockdispatch_asynccheckBlockdog
__strongdog_weakdispatch_asyncdog_strongdispatch_asyncdog_strong

Guess you like

Origin blog.csdn.net/wujakf/article/details/129182902