Block
There will be problems with circular references when used, andBlock
the situation of multiple nested uses is more complicated, and there will also benil
problems 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 Block
accessing external variables is a value copy (deep copy) for temporary variables, and __block
a 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 block
released after execution; if Block
the object type is accessed (for example: Dog类
), Block
accessing the external variable will __strong
modify the object ( NSObject
the default is __strong
modified ), is the function of the executed object itself copy
(shallow copy).
- 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 name
has been modified to @"张三"
prove again that block
the captured object is a shallow copy, the reference count is +1, and when testBlock
the function call is completed, block1
it is released, and self
the reference count of the pair is -1, so there will be no circular reference.
- Circular reference situation
actually means that the object will be held forever, and the object cannot be released,dealloc
so 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.
__weak principle
To solve circular references, the most we can do is __weak
to modify variables to break the chain of strong references. So __weak
why can it be done?
Runtime maintains a weak_table_t
hash table to store all weak
pointers to an object. Key
is the address of the pointed object, Value
and is weak
the 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
runtime
will be calledNSObject.mm
objc_initWeak
id objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
- *location:
__weak
pointer address of weakSelf - newObj: the object referenced, that is, in the example
self
objc_initWeak
The function will callstoreWeak
the 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_rootDealloc
method 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_async
It is executed asynchronously by the main thread. It may be checkBlock
executed after the function is executed, and the object dog
will be released before, resulting in a weak reference dog_weak
pointer nil
.
We __strong
use 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_async
Here, 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.checkBlock
dispatch_async
checkBlock
dog
__strong
dog_weak
dispatch_async
dog_strong
dispatch_async
dog_strong