讨论主题:
什么是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的机会,只有以下条件 同时满足:
- 外层没有其他引用 执行 Self对象
- Self对象没有接受过autorelease消息
- 发现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指针活跃并且仍然指向同一个对象。
交流感言
尽管这个问题十分小到甚至我们很少有机会会在这个地方去犯错,细节决定成败。
愿这夜深人静时的琐碎思想能给自己一次洗礼,走好明天的路,要任何时候都从小事做起。
网友评论
『顺着我刚才讲的: @广州-黄锦辉 弱引用对象如果被释放,倒不会造成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]还没有执行完,这样才是崩溃原因吧。
如果可以请联系下我,企鹅-59-2296073
跪谢;
这片文章算是醍醐灌顶, 受益匪浅;