[读书笔记]iOS与OS X多线程和内存管理 [Blocks部分-4]

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 ablock;
    {
       
id array=[[NSMutableArrayalloc]init];
        ablock=^(
id objct){
            [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 __weak tmp=self;
   
blk_=^{
       
NSLog(@"self %@",tmp);
    };
   
return self;
}
在该代码中,由于当Block存在时,持有此Block的Person对象肯定存在(blk_属于Person对象),所以不需要对tmp进行判空。
在iOS4及更低版本中,没有ARC机制,使用__unsafe_unretained修饰符代替__weak修饰符。
下面的代码也会造成循环引用,因为Block语法内使用的obj属于对象的成员变量,想要截获对象的成员变量,自然也截获了对象本身self.
//arc打开状态
@interface Person(){
   
void(^blk_)(void);
   
id obj;
}
@end
@implementation Person
-(
void)dealloc{
   
NSLog(@"person dealloc");
}
-(
id)init{
   
self=[superinit];
   
blk_=^{
       
NSLog(@"obj %@",obj);
    };
   
return self;
}

使用__block变量也能避免循环引用,如下代码:
//arc打开状态
@interface Person(){
   
void(^blk_)(void);
}
@end
@implementation Person
-(
void)dealloc{
   
NSLog(@"person dealloc");
}
-(
id)init{
   
self=[superinit];
   
__block id tmp=self;
   
blk_=^{
       
NSLog(@"obj %@",tmp);
       tmp=
nil;
    } ;
   
return self;
}
-(
void)execBlock{
   
blk_();
}

该代码没有引起循环引用,但是如果不调用execBlock这个方法,即不执行对tmp的赋值,就会引起循环引用。
  • Person类对象持有Block;
  • Block持有__block变量
  • __block持有Person类对象
通过执行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部分到此结束。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。