iOS:Block 循环引用问题

35次阅读

共计 3051 个字符,预计需要花费 8 分钟才能阅读完成。

循环引用是一个比较常见的问题,之前面试的时候也会被问到,如何解决循环引用问题,其实大家都知道使用__block,__weak 这些修饰符可以解决循环引用问题,那今天我们要讨论的就是他们是怎么样解决了循环引用问题的。
__weak
其实__weak 是比较好理解的,它的作用就是在两方相互强引用的时候,把其中一个引用变为弱引用,打破这个循环引用的圈。
我们通过代码看一下。
MyPerson * person = [[MyPerson alloc] init];
person.age = @”10″;
__weak typeof(person) weakPerson = person;
person.block = ^{
NSLog(@”age is %@”, weakPerson.age);
};

MyPerson 类里面有一个 block,一个 string 类型的 age,在执行 block 的时候,打印了 age,如果不用 weakPerson 的话,就会产生循环引用,这种用法想必大家都很熟悉。
那我们看一下编译后的 cpp 文件。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MyPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到 block 内部捕获到的是 MyPerson *__weak weakPerson;,所以不会产生强引用,自然也就不会出现循环引用问题。
__weak 只在 ARC 环境下使用。
__block
最开始我以为__block 消除循环引用的方式跟__weak 是一样的。
// 这种用法 ARC 环境下是错的 MRC 可以
MyPerson * person = [[MyPerson alloc] init];
person.age = @”10″;
__block typeof(person) weakPerson = person;
person.block = ^{
NSLog(@”age is %@”, weakPerson.age);
};
我们现在开发一直都是在 ARC 环境下,首先自己检讨一下,我一直都以为__block 可以这么用,而且关键是这样用了确实编译器就没有了关于循环引用的警告了。
但是我们如果重写一下 MyPerson 类的 dealloc 方法,让对象释放时打印点东西,你会发现如果使用__weak,在 main 函数结束时,person 会调用 dealloc 释放,但是如果像上面一样用__block,person 不会释放,还是存在循环引用。
我强调了这种用法在 ARC 环境下不可以,但是在 MRC 环境下是可以的,因为 MRC 环境下 block 不会对__block 修饰的属性强引用。
下面是 ARC 环境正确的__block 使用方式。
如果就按照__weak 的使用方法使用,在 block 内部把 weakPerson 置为 nil,同时这个 block 必须要调用。
MyPerson * person = [[MyPerson alloc] init];
person.age = @”10″;

__block typeof(person) weakPerson = person;
person.block = ^{
NSLog(@”age is %@”, weakPerson.age);
weakPerson = nil;
};
person.block();// 必须有这个代码
也可以这样写:
__block MyPerson * person = [[MyPerson alloc] init];
person.age = @”10″;
person.block = ^{
NSLog(@”age is %@”, person.age);
person = nil;
};

person.block();
两种写法都一样,必须手动置为 nil,然后必须执行 block。下面我们说一下原理。
首先呢,我们上面已经说了,如果不手动置为 nil 的话,使用__block 依然有循环引用,我们结合 cpp 的代码分析一下具体循环引用在什么地方。
我们编译下面这种写法。
MyPerson * person = [[MyPerson alloc] init];
__block typeof(person) weakPerson = person;
person.age = @”10″;
person.block = ^{
NSLog(@”age is %@”, weakPerson.age);
};
我们来分析一下下面的代码
struct __Block_byref_weakPerson_0 {
void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
typeof (person) weakPerson;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakPerson_0 *weakPerson; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
其实这个__block 属性的作用咱们之前已经说过了,就是在 block 内部把修饰的属性包装成一个对象,也就是这个__Block_byref_weakPerson_0。__Block_byref_weakPerson_0 内部有我们的 weakPerson 属性,typeof (person) weakPerson,这里只是叫 weakPerson,他的持有方式还是 strong 的。
所以说我们可以分析出来,__block 属性持有我们的 person 变量,person 持有 block,block 内部持有这个__block 属性,就像下面这个图示一样。

我们通过置为 nil 解决它循环引用的方式,就是打断一条强引用。如下图

__unsafe_unretained
ARC 环境下__unsafe_unretained 与__weak 使用方法相同。
MRC 环境下,与__block MRC 环境下的使用一样。
__unsafe_unretained 和__weak 对比:
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为 nil
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变

正文完
 0