2.3.5 __block变量存储域
Block变量从栈复制到堆时对__block变量产生的影响
__block变量存储域 |
影响 |
栈 |
从栈复制到堆并被Block持有 |
堆 |
被Block持有 |
在多个Block中使用__block变量时,第一个Block从栈复制到堆时,__block变量也一并从栈复制到堆。在之后的Block从栈复制到堆时,__block变量被Block持有,__block变量引用计数增加。之前__block变量结构中的__forwarding能够做到“不管__block变量在栈上还是堆上都能访问”,原因是栈上的__block变量在复制到堆上时,他的成员变量__forwarding的值替换为复制后堆上的__block变量的地址。
2.3.6 截获对象
看下面代码:
typedef void (^aBlock)( id); aBlock { id ablock=^(id [array addObject:objct]; NSLog(@"array count %d",[arraycount]); }; } ablock([[NSObjectalloc]init]); ablock([[NSObjectalloc]init]); |
作者写到:“执行该代码后,程序会强制结束,array随变量作用域的结束而被废弃”。但在我的实际运行中,array对象正常使用,程序也正常运行(Xcode5.1.1和Xcode6.1)。所以可能苹果已经修改这方面的机制,作者关于截获对象的结论不再列出,使用时截获对象的规则可参照截获自动变量的规则。
这一节提到_Block_object_assign函数和_Block_objec_dispose函数,前者相当于retain方法,将对象赋值在对象类型的结构体成员变量中(例如上面的代码中将一个对象加入到array中);后者相当于release方法,释放赋值在对象类型的结构体成员变量中的对象(如从array中释放某个object)。
以下情况Block会从栈复制到堆:
- 调用Block的copy实例方法
- Block作为函数返回值
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时。
除以下情景外,推荐调用Block的copy实例方法(即如果系统已自动调用,我么就不需调用):
- Block作为函数值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中传递Block时。
2.3.7 __block 对象和变量
此节沿用上节结论,略过
2.3.8 Block循环引用
Person.m文件代码 主程序代码: { ... Person *per=[[Personalloc]init]; NSLog(@"o"); ... } |
执行结果是该源码中的dealloc方法未被调用.原因是,Person类的对象持有blk_,blk_中持有id类型变量self.并且由于Block语法赋值给成员变量blk_(此时为ARC模式,变量默认为__strong类型),所以栈上的Block复制到堆,并持有所使用的self,这就造成了循环引用。编译器也给出了警告。
为避免循环引用可使用__weak修饰变量,并将self赋值给变量使用。如下:
-(id)init{ self=[superinit]; id blk_=^{ NSLog(@"self %@",tmp); }; return } |
在该代码中,由于当Block存在时,持有此Block的Person对象肯定存在(blk_属于Person对象),所以不需要对tmp进行判空。
在iOS4及更低版本中,没有ARC机制,使用__unsafe_unretained修饰符代替__weak修饰符。
下面的代码也会造成循环引用,因为Block语法内使用的obj属于对象的成员变量,想要截获对象的成员变量,自然也截获了对象本身self.
//arc打开状态 @interface void(^blk_)(void); id } @end @implementation -(void)dealloc{ NSLog(@"person dealloc"); } -(id)init{ self=[superinit]; blk_=^{ NSLog(@"obj %@",obj); }; return } |
使用__block变量也能避免循环引用,如下代码:
//arc打开状态 @interface void(^blk_)(void); } @end @implementation -(void)dealloc{ NSLog(@"person dealloc"); } -(id)init{ self=[superinit]; __block blk_=^{ NSLog(@"obj %@",tmp); tmp=nil; } ; return } -(void)execBlock{ blk_(); } |
该代码没有引起循环引用,但是如果不调用execBlock这个方法,即不执行对tmp的赋值,就会引起循环引用。
通过执行execBlock,__block变量tmp对Person类对象的强引用失效。 |
使用__block变量避免循环引用有如下优点,
- 通过__block变量可控制对象的持有时间
- 在不能使用__weak修饰符的环境下代替__unsafe_unretained修饰符,不必担心悬垂指针。
缺点是为避免循环引用必须执行Block。
2.3.9 copy/release
ARC无效时,需要手动将Block从栈复制到堆,用copy方法来复制,release方法来释放。只要Block位于堆上,则可以通过retain方法持有,对于栈上的Block调用retain方法不起任何作用。在c语言中也可以使Block,此时使用Block_copy和Block_release代替copy/release实例方法。另外,ARC无效时,__block说明符被用来避免Block循环引用,这是因为Block从栈复制到堆时,若Block使用的变量为附有__block说明符的id类型对象或对象类型自动变量,不会被retain,反之如果没有__block说明符则会被retain。
在ARC有效和无效时,__block说明符的用途大不一样,请注意。
Block部分到此结束。