V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
jessefang
V2EX  ›  iDev

关于 block 循环引用的疑问

  •  
  •   jessefang · 2016-11-09 15:29:15 +08:00 · 2853 次点击
    这是一个创建于 2943 天前的主题,其中的信息可能已经有所发展或是发生改变。
    在下面代码里为什么要将 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;
    }
    9 条回复    2016-11-23 11:04:04 +08:00
    nathanw
        1
    nathanw  
       2016-11-09 21:48:08 +08:00   ❤️ 1
    object 持有 blk , elk 又通过 block 持有 object 。
    相互引用,除非一方置为 nil ,否则无法释放。
    iOran
        2
    iOran  
       2016-11-09 23:13:52 +08:00   ❤️ 1
    这段代码,建议楼主结合出处<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
        3
    jessefang  
    OP
       2016-11-10 10:01:25 +08:00
    @iOran @nathanw 还有一点疑惑,如果这里的 block 获取的不是 self ,而是 self 所持有的属性,为什么不置为 nil 也可以正常释放呢?
    iOran
        4
    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
        5
    jessefang  
    OP
       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
        6
    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
        7
    iOran  
       2016-11-10 12:28:50 +08:00   ❤️ 1
    这玩意看代码太痛苦了。
    jessefang
        8
    jessefang  
    OP
       2016-11-10 13:58:13 +08:00
    @iOran 谢谢,写的很好呀,一目了然
    miketeam
        9
    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 ;
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1040 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 19:28 · PVG 03:28 · LAX 11:28 · JFK 14:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.