【GeekBand】block中weak-strong dance 的讨论整理

讨论主题:

什么是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指针活跃并且仍然指向同一个对象。

交流感言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容