关于 block 循环引用的疑问

2016-11-09 15:29:15 +08:00
 jessefang
在下面代码里为什么要将 tmp 置为 nil ?在 blk_执行完后 tmp 不会自己释放的么?

typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
__block id tmp = self;
blk_ = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
};
return self;
}
- (void)execBlock
{
blk_();
}

@end
int main()
{
id object = [[MyObject alloc] init];
[object execBlock];
return 0;
}
2849 次点击
所在节点    iDev
9 条回复
nathanw
2016-11-09 21:48:08 +08:00
object 持有 blk , elk 又通过 block 持有 object 。
相互引用,除非一方置为 nil ,否则无法释放。
iOran
2016-11-09 23:13:52 +08:00
这段代码,建议楼主结合出处<Objective-C 高级编程>来理解,尤其是理解 block 的 C 语言实现。

首先,如 @nathanw 所说, MyObject 的实例 持有 block ; block 中, block 的结构体(block 底层 C 实现是 struct) 又持有 __strong 类型的 self 对象,此时,如果不在 block 执行完之后,将 block 中的 self 设置为 nil ,两者的循环强引用无法打破。

但本段代码也有缺点,如果 execBlock 实例方法不执行,也就是 blk();不执行,实际上没有机会将 id __strong MyObject var 设置为 nil ,也会有循环引用的问题。

合适的解决办法是:将 self 设置为 __weak 类型,防止双方强持有,也就解决问题了。
jessefang
2016-11-10 10:01:25 +08:00
@iOran @nathanw 还有一点疑惑,如果这里的 block 获取的不是 self ,而是 self 所持有的属性,为什么不置为 nil 也可以正常释放呢?
iOran
2016-11-10 10:40:33 +08:00
你确定 “如果这里的 block 获取的不是 self ,而是 self 所持有的属性,不设置为 nil 也可以正常释放” 是正确的吗?

据我所知,这种也是会有循环引用的问题。不信你可以试试如下代码:

/////////////////////// 只是换了 MyObject 的定义,其他一样

@interface MyObject : NSObject
{
blk_t blk_;
id obj;
}
@end

@implementation MyObject
- (id)init
{
self = [super init];
blk_ = ^{
NSLog(@"self = %@", obj);
};
return self;
}

- (void)execBlock
{
blk_();
}

@end

//////////////////////

编译器是会报错的: ./YourProj/Test.m:17:24: Capturing 'self' strongly in this block is likely to lead to a retain cycle

上 Xcode 敲一敲试试吧。另外,你也可以用 clang -rewrite-objc Test.m 来查看它的底层 C 实现。
jessefang
2016-11-10 11:19:49 +08:00
@iOran
@interface MyObject : NSObject
{
blk_t blk_;
id obj;
}
@end

@implementation MyObject
- (id)init
{

self = [super init];
__block id tmp = obj //用__block 修饰这个属性

blk_ = ^{
NSLog(@"self = %@", tmp);
};
return self;
}

- (void)execBlock
{
blk_();
}

@end

///////////////////////////////////////////
不好意思,我没说清楚,我的情况是以上的代码,可以正常释放
iOran
2016-11-10 12:28:30 +08:00
总的来说,这和 __block 这个关键字有关系。

这里有种细节可以说明下:
1. id obj; 实际上是:__strong id obj; 这里 obj 会有__strong 修饰符号;
2. 任何有__strong 或者是有__block 或者两种修饰符同时有的变量,在转换成 block 对象时,也就是转换成底层对象时候,会额外生成两个函数,一个叫__main_block_copy_0(它底层会调用_Block_object_assign),另一个叫__main_block_dispose_0(它底层会调用_Block_object_dispose)。这两个函数在栈拷贝 block 入堆 以及 从堆上释放内存块的时候调用。
3. 你用__block 或者__strong 修饰变量,这两种情况,被修饰的对象,都会在函数 block 被生成的时候由栈区入堆区。
4. 拷贝使用 copy ,释放使用 dispose.

最后来看你的 block 的底层实现,我们要关心两部分内容,这是第一部分:
/////////////////////////////////////////// 你看到的:
blk_ = ^{
NSLog(@"self = %@", tmp);
};
/////////////////////////////////////////// 使用 clang -rewrite-objc file.m 转换后的:
// @implementation MyObject
struct __Block_byref_tmp_0 {
void *__isa;
__Block_byref_tmp_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id tmp;
};

第二部分:
/////////////////////////////////////////// 你看到的:
blk_();
/////////////////////////////////////////// 使用 clang -rewrite-objc file.m 转换后的:
static void __MyObject__init_block_func_0(struct __MyObject__init_block_impl_0 *__cself) {
__Block_byref_tmp_0 *tmp = __cself->tmp; // bound by ref

NSLog((NSString *)&__NSConstantStringImpl__var_folders_zg_tjgxj12j793ccbxmtsg3l_0m0000gn_T_Test_b86496_mi_0, (tmp->__forwarding->tmp));
}

看到没,两部分里面的 tmp(第一部分就是 id tmp ,第二部分是 tmp->__forwarding->tmp),和 self 没扯上关系。
iOran
2016-11-10 12:28:50 +08:00
这玩意看代码太痛苦了。
jessefang
2016-11-10 13:58:13 +08:00
@iOran 谢谢,写的很好呀,一目了然
miketeam
2016-11-23 11:04:04 +08:00
temp ( xxxx66666 ) ---> someInstanceOf "MyObject" xxxx123;

someInstanceOf "MyObject" ---> [- (id)init (xxxxxxxxxxx222222222) have [ temp(&p--->xxxx66666)----> xxxx123] ]

so:
temp ---> someInstanceOf "MyObject" xxxx123;

someInstanceOf "MyObject" xxxx123-----------------> temp

?????

temp = nil , or someInstanceOf "MyObject"=nil ???
just temp = nil ;

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/319287

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX