美文网首页iOS-blockiOS开发iOS 原理
【GeekBand】block中weak-strong danc

【GeekBand】block中weak-strong danc

作者: 星期五__ | 来源:发表于2015-12-02 10:32 被阅读2879次

    讨论主题:

    什么是weak-strong dance,为什么AFNetWorking中要这样使用block

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
    strongSelf.networkReachabilityStatusBlock(status);
    }
    };
    

    讨论内容:

    精华回复:

    李建忠

    要很好地理解这个两个基本功底:1. 理解block的内存模型 2. 理解ARC里面的循环引用问题
    然后教大家一个技巧就是:画内存模型图!

    陈铭嘉
    李老师,一般我知道要用__weak字段加在self之前来防止block和self实例的循环引用,为什么这里在block中它又要将它转回strong呢

    杨武
    这个问题问得好

    李建忠
    好问题,启发一下,想想 弱引用 有一个不靠谱的特点是什么?

    陈铭嘉
    我只知道弱引用是不加引用计数的,强引用会加引用计数。。

    李建忠
    对,不加引用计数的 后果是什么?

    陈铭嘉
    但这里应该是不加引用计数啊。不然不是和block对象产生循环了吗?

    李建忠

    弱引用最大的风险就是 在使用过程当中,其引用的对象随时可能被ARC给释放掉

    船长

    那不然要弱应用干嘛

    陈铭嘉
    就是说弱引用的设计存在是为了规避循环引用,其带来的缺点是随时会被析构,意思是说在AFNetWorking的block中使用弱引用的self会被随时析构?

    江夏沐

    随时被释放掉的这种情况出现……得怪程序员吧。而且一般用到弱引用也都是属于短时间的调用。

    黄锦辉

    这我不理解了,弱引用self确实有可能导致在block运行时self是空的,也不会crash

    江夏沐

    随时有点夸张了……只能说是有个可能性。

    杨武

    这个问题需要理解:变量定义,将对象引用赋值给变量时 ARC 做了什么,变量有效范围结束时 ARC 又会做什么,block capturing 时发生了什么,将 block 放到 ivar 里又发生了什么,最后,self 的有效范围是什么。

    黄锦辉

    但在block里这样转,假如self释放了,不是会导致悬垂指针吗?

    李建忠

    @广州-黄锦辉 弱引用对象如果被释放,倒不会造成C++那样的空悬指针,但会被置为nil

    杨武

    apple 官方文档里有这个问题的解释,不过,能自己想明白是最好的

    李建忠
    @汕头—黄穆斌 可能性=随时

    江夏沐
    [尴尬]好吧。

    陈铭嘉
    回杨武老师:1.ARC环境下引用赋值应该默认是浅拷贝吧
    2.block capturing时应该是对值类型的值拷贝,对引用类型的浅拷贝吧
    3.block本身也算一个伪对象,放到ivar里和普通对象应该是一样的吧
    4.self实例的有效范围是指它的引用计数置0的这段时间范围吗?

    杨武

    id myVar = objRef; 是 myVar <— objRef, [objRef retain]; 而 { id myVar ... },在 } 这里有一个 [myVar release]。block 不是什么伪对象,它就是一个对象,block 定义时是生成在 stack 内存里的,当将其存入 ivar 时,ARC 会通过 [block copy] 将其复制到堆上。

    陈铭嘉

    杨武老师,普通的类对象是不会在copy时从一个NSStackBlock类型变成NSMallocBlock类型,一个类型完全变成另外一个类型,只有block是这样,所以说它是一个特殊的对象,即伪对象。我看有些人是这样称呼的

    杨武

    这些人都没有看 WWDC 视频吧,我们跟 apple 的人保持口径一致比较好

    陈铭嘉

    好吧..其实跑题了,我还是没明白为什么在AFNetworking里要用weak-strong dance

    杨武

    算了,去看
    https://developer.apple.com/library/mac/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226-CH1-SW4 里的 Use Lifetime Qualifiers to Avoid Strong Reference Cycles

    李建忠

    顺着我刚才讲的: @广州-黄锦辉 弱引用对象如果被释放,倒不会造成C++那样的空悬指针,但会被置为nil,那如果把weakSelf置成nil,block里面的代码调用 不就出错了吗?,所以这时候 先将weakSelf的 弱引用 转换成strongSelf 这样的强引用指针。ARC就不会 释放对象啦(因为强引用的retain count 至少为1)

    陈铭嘉

    那以后block教程为什么不都用weak-strong dance..可是实际上一般大家就用_weak就行了,也没见到weakself被析构嘛,至少我用weakself一直没出啥事..

    李建忠

    但 一定注意strongSelf是一个 局部变量,它的生存周期比weakSelf要短。所以strongSelf所在的函数调用结束,它就不再retain对象。 对象的引用又回归弱引用,体现出来就是,刚开始是 弱引用,转型之后是强引用,最后又成 弱引用这样的状态

    UFO

    如果在block里面,第一行把弱self转成strong的时候,已经为nil了呢,这个情况貌似不会发生,为啥呢

    李建忠

    @UFO 我正要说,有可能发生。所以 上面的写法 不严谨,严禁的写法 就是 杨武给的 Apple官方文档那一节的写法。
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    然后后面要加一句:if (strongSelf){......}

    UFO

    [发呆]我一直以为af这么写是没啥问题的

    李建忠
    所以,要读官方文档

    陈铭嘉
    李老师,打断一下,我突然倒觉得是AFNetworking是自己开辟一个线程和runloop,在多线程下才会用到这种场景吧,单线程也要这样做吗

    李建忠

    @上海-陈铭嘉 那是因为有时候 对象以 autorelease的消息,缓存在内存中。ARC没有触发立即的释放。 但并不意味着 weakSelf一直有效
    Block的引用循环 和 弱引用问题 与 单线程、多线程无关。只与内存模型 和 生存周期有关

    UFO

    感觉这里不太合理,就算加了判断,程序不会崩溃,感觉业务也无法继续了,

    江夏沐

    [衰]本来觉得我这一块理解还可以的……看完大家的讨论,我反而彻底的晕了。

    李建忠

    除了autorelease之外的延迟效应,通常的情况是,外层有引用 指向 这个self对象。self 指向block, block里面有一个弱引用 回指向self。
    由于外层的引用不释放,那么里面的这些 引用 都有效
    那个weakSelf 变为nil的机会,只有以下条件 同时满足:

    1. 外层没有其他引用 执行 Self对象
    2. Self对象没有接受过autorelease消息
    3. 发现Self现在的retain count为0,ARC触发 回收Self对象,然后将weakSelf置为nil

    陈铭嘉
    好的,谢谢李老师解答,我试试看画一下,看看合不合您的意思

    李建忠
    实际上,大家 通常觉得weakSelf没有变为nil,是因为第一个条件 就不满足,也就是通常外层有其他 强引用 指向这个self

    陈铭嘉

    WeChat_1448958286.jpeg

    李建忠

    这就是 苹果官网讲的: For non-trivial cycles, however, you should use:的意思
    两个差号出现的时候,通常 这段代码你已经离开了。那这时候weakSelf为nil 也无所谓了,但你要不做 那个if (strongSelf)判断,这个异常是有可能抛出来的。虽然 几率极低极低,这就是苹果讲的 这就是 苹果官网讲的 non-trivial cycles
    我建议大家有空可以做一个实验,把外层引用都清空,避免autorelease,然后在那个任务中 写一个大循环 甚至死循环,不要做判断,看看weak Self为nil的情况是否会出现

    我的最终实验:

    实验目的:
    保护了block中的weakSelf没有在代码执行期间析构。

    实验思路:
    创建2个线程-主线程和线程2,主线程在for循环到500时,指针置空,外部引用为0,而线程2在for循环1000次输出

    可能结果:
    假如在主线程for循环到500时,block停止执行,即实验失败
    假如在主线程for循环到500时,block仍然成功执行到1000,即成功

    开始先尝试在没有weak-strong dance下运行代码:
    实验类BLNPoint.m类代码:

    - (void) netwrokDataBack{
        __weak __typeof(self)weakSelf = self;
            SuccBlock block = ^(int data){
                for (int i = 0; i < 1000; i ++) {
                [weakSelf go:i];
                }
            };
            block(11);
    }
    
    -(void)go:(int)number{
        NSLog(@"BLOCK GO %d",number);
    }
    

    相关调用代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self blockTest];
    }
    
    -(void)blockTest{
            __block BLNPoint *point = [BLNPoint new];
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            dispatch_async(queue, ^{
                    [point netwrokDataBack];
            });
        for (int i = 0; i < 501; i ++) {
            NSLog(@"Point will die");
            if (i == 500) {
                point = nil;
                NSLog(@"NO REFERENCE");
            }
        }
        NSLog(@"OVER");
    }
    

    输出结果:2015-12-01 16:56:37.951 Sample[2103:375766] BLOCK GO 384
    2015-12-01 16:56:37.960 Sample[2103:375679] Point will die
    2015-12-01 16:56:37.960 Sample[2103:375766] BLOCK GO 387
    2015-12-01 16:56:37.961 Sample[2103:375679] Point will die
    2015-12-01 16:56:37.961 Sample[2103:375766] BLOCK GO 388
    2015-12-01 16:56:37.961 Sample[2103:375679] NO REFERENCE
    2015-12-01 16:56:37.961 Sample[2103:375679] OVER

    现在来换上weak-strong dance。

    - (void) netwrokDataBack{
        __weak __typeof(self)weakSelf = self;
            SuccBlock block = ^(int data){
                __strong __typeof(weakSelf)strongSelf = weakSelf;
                for (int i = 0; i < 1000; i ++) {
                [strongSelf go:i];
                }
            };
            block(11);
    }
    

    再次运行,不难看出成功保护了block中的内容:

    2015-12-01 17:00:47.602 Sample[2116:380700] Point will die
    2015-12-01 17:00:47.602 Sample[2116:380753] BLOCK GO 396
    2015-12-01 17:00:47.602 Sample[2116:380700] Point will die
    2015-12-01 17:00:47.602 Sample[2116:380753] BLOCK GO 397
    2015-12-01 17:00:47.602 Sample[2116:380700] NO REFERENCE
    2015-12-01 17:00:47.602 Sample[2116:380753] BLOCK GO 398
    2015-12-01 17:00:47.602 Sample[2116:380700] OVER
    2015-12-01 17:00:47.602 Sample[2116:380753] BLOCK GO 399
    2015-12-01 17:00:47.602 Sample[2116:380753] BLOCK GO 400
    2015-12-01 17:00:47.602 Sample[2116:380753] BLOCK GO 401
    2015-12-01 17:00:47.603 Sample[2116:380753] BLOCK GO 402
    2015-12-01 17:00:47.603 Sample[2116:380753] BLOCK GO 403
    2015-12-01 17:00:47.603 Sample[2116:380753] BLOCK GO 404

    内容总结

    1.block是一个对象,理解block,我们必须先要要了解block的内存模型,block对应的结构体定义如下:
    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    
    struct Block_layout {
        void *isa;
        int flags;
        int reserved;
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };
    
    • isa 指针,所有对象都有该指针,用于实现对象相关的功能。
    • flags,用于按 bit 位表示一些 block 的附加信息。
    • reserved,保留变量。
    • invoke,函数指针,指向具体的 block 实现的函数调用地址。
    • descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
    • variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

    2.[重要]破解ARC里面的循环引用问题,通常情况下,我们可以使用weak属性来解除循环引用,但另外一种情况下,我们可以手动将指针置为nil,假如程序员足够仔细的情况下,手动置空指针优于weak属性。

    typedef void(^SuccBlock)(id data);
    @interface NetworkClass {
        SuccessBlock _sucBlock;
    }
    @property (nonatomic,assign)BOOL propertyUseInCallBack;
    - (void) requestWithSucBlock: (SuccessBlock) callbackBlock;
    @end
     
    @implementation NetworkClass
    - (void) requestWithSucBlock: (SuccessBlock) callbackBlock {
        _sucBlock = callbackBlock;//MRC下:_sucBlock = [callbackBlock copy]; 不copy block会在栈上被回收。
    }
     
    - (void) netwrokDataBack: (id) data {
        if (data != nil && _sucBlock != NULL) {
            _sucBlock(data);
        }
        //MRC下:要先将[_sucBlock release];(之前copy过)
        _sucBlock = nil; //Importent: 在使用之后将Block赋空值,解引用 !!!
    }
    @end
    //=======================以下是使用方===========================
    @implementation UserCode
    - (void) temporaryNetworkCall
    {
        NetworkClass *netObj = [[NetworkClass alloc] init];
        netObj.propertyUseInCallBack = NO;
        [netObj requestWithSucBlock: ^(id data) {
            //由于block里面引用netObj的指针所以这里产生了循环引用,且由于这个block是作为参数传入对象的,编译器不会报错。
            //因此,NetworkClass使用完block之后一定要将作为成员变量的block赋空值。
            if (netObj.propertyUseInCallBack == YES) {
                //Do Something...
            }
        }];
    }
    @end
    
    3.block中的weakSelf对象会存在以下两种情况的危险情况:

    ----- autorelease的延迟效应
    ----- block外对self的外部引用为0(例子见上述实验)

    4.附上官方文档用LifeTime修饰词避免强引用循环中列举的三个例子:
    • 你可以使用__block修饰词,然后就能在completion handler把myController变量置为nil
    MyViewController * __block myController = [[MyViewController alloc] init…];
    // ...
    myController.completionHandler =  ^(NSInteger result) {
        [myController dismissViewControllerAnimated:YES completion:nil];
        myController = nil;
    };
    
    • 或者你也可以使用weak变量,下面是一个简单的效果例子:
    MyViewController *myController = [[MyViewController alloc] init…];
    // ...
    MyViewController * __weak weakMyViewController = myController;
    myController.completionHandler =  ^(NSInteger result) {
        [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
    };
    
    • 为了解决non-trivial cycles,你可以这样使用:
    MyViewController *myController = [[MyViewController alloc] init…];
    // ...
    MyViewController * __weak weakMyController = myController;
    myController.completionHandler =  ^(NSInteger result) {
        MyViewController *strongMyController = weakMyController;
        if (strongMyController) {
            // ...
            [strongMyController dismissViewControllerAnimated:YES completion:nil];
            // ...
        }
        else {
            // Probably nothing...
        }
    };
    

    在某些场合,我们还可以对于那些无法匹配__weak字段的类使用____unsafe_unretained 字段,然而它在实际中仍然无法解决non-trivial cycles问题,因为它很难甚至不可能来保持__unsafe_unretained指针活跃并且仍然指向同一个对象。

    交流感言

    尽管这个问题十分小到甚至我们很少有机会会在这个地方去犯错,细节决定成败。
    愿这夜深人静时的琐碎思想能给自己一次洗礼,走好明天的路,要任何时候都从小事做起。

    相关文章

      网友评论

      • 806349745123:感觉讨论没有切到点上,比如:
        『顺着我刚才讲的: @广州-黄锦辉 弱引用对象如果被释放,倒不会造成C++那样的空悬指针,但会被置为nil,那如果把weakSelf置成nil,block里面的代码调用 不就出错了吗?,所以这时候 先将weakSelf的 弱引用 转换成strongSelf 这样的强引用指针。ARC就不会 释放对象啦(因为强引用的retain count 至少为1)』
        weakSelf为nil,给nil调用方法,objc_msgSend(nil, ...)不会崩溃吧?更何况你给实验也不会crash。
        crash的原因个人是闭包里面的对象obj因为是weak修饰的,而且和闭包外部不同步,比方说我在block里面[obj doSomeThing]的途中,obj指向的对象在外部被销毁了,__weak修饰的obj这时候被置为nil,然而[obj doSomeThing]还没有执行完,这样才是崩溃原因吧。

        806349745123:加一句,doSomething包括kvo等之类方法,不是说给nil调用方法就会崩溃
      • 09604ab1d8b7:我想加入这个微信群,保证不发广告,只求能官网,希望楼楼能成全;
        如果可以请联系下我,企鹅-59-2296073
        跪谢;
        这片文章算是醍醐灌顶, 受益匪浅;
        09604ab1d8b7:@与狼同行 忘记看时间了....还是谢谢回复了;
        星期五__:@丨卷毛丶 当时iOS火的时候,跟几个大牛稍微学了会,现在都方向都分散了,都快2.3年前了,我也不大研究了
      • 04b60abf8d64:xcode7.3开始就不需要强弱(weak strong dance)引用
        深证铁板烧豆芽:有出处吗?
      • d9557f883fd8:我xcode7.3 你测试的demo貌似跑起来都是到OVER就停下来了

      本文标题:【GeekBand】block中weak-strong danc

      本文链接:https://www.haomeiwen.com/subject/nqbjhttx.html