本文不讲block如何声明及使用,只讲block在使用过程中暂时遇到及带来的隐性危险。
主要基于两点进行演示:
1.block 的循环引用(retain cycle)
2.去除block产生的告警时,需注意问题。
有一次,朋友问我当一个对象中的block块中的访问自己的属性会不会造成循环引用,我哈绰绰的就回了一句,不会。兄弟,看完这个,希望你能理解我为什么会说不会循环引用。别废话,演示开始。
下面是我专们写了一个类来演示:
头文件.h
//
//  BlockDemo.h
//  blockDemo
//
//  Created by apple on 14-7-24.
//  Copyright (c) 2014年 fengsh. All rights reserved.
/*
 -fno-objc-arc
 
 由于Block是默认建立在栈上, 所以如果离开方法作用域, Block就会被丢弃,
 在非ARC情况下, 我们要返回一个Block ,需要 [Block copy];
 
 在ARC下, 以下几种情况, Block会自动被从栈复制到堆:
 
 1.被执行copy方法
 2.作为方法返回值
 3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时
 4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候.
 */
#import <Foundation/Foundation.h>
@class BlockDemo;
typedef void(^executeFinishedBlock)(void);
typedef void(^executeFinishedBlockParam)(BlockDemo *);
@interface BlockDemo : NSObject
{
    executeFinishedBlock finishblock;
    executeFinishedBlockParam finishblockparam;
}
/**
 *  执行结果
 */
@property (nonatomic,assign) NSInteger resultCode;
/**
 *  每次调用都产生一个新对象
 *
 *  @return
 */
+ (BlockDemo *)blockdemo;
/**
 *  不带参数的block
 *
 *  @param block
 */
- (void)setExecuteFinished:(executeFinishedBlock)block;
/**
 *  带参数的block
 *
 *  @param block
 */
- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block;
- (void)executeTest;
@end
//
//  BlockDemo.m
//  blockDemo
//
//  Created by apple on 14-7-24.
//  Copyright (c) 2014年 fengsh. All rights reserved.
//
#if __has_feature(objc_arc) && __clang_major__ >= 3
    #define OBJC_ARC_ENABLED 1
#endif // __has_feature(objc_arc)
#if OBJC_ARC_ENABLED
    #define OBJC_RETAIN(object)         (object)
    #define OBJC_COPY(object)           (object)
    #define OBJC_RELEASE(object)        object = nil
    #define OBJC_AUTORELEASE(object)    (object)
#else
    #define OBJC_RETAIN(object)           [object retain]
    #define OBJC_COPY(object)             [object copy]
    #define OBJC_RELEASE(object)          [object release], object = nil
    #define OBJC_AUTORELEASE(object)      [object autorelease]
#endif
#import "BlockDemo.h"
@implementation BlockDemo
+ (BlockDemo *)blockdemo
{
    return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
}
- (id)init
{
    self = [super init];
    if (self) {
        NSLog(@"Object Constructor!");
    }
    return self;
}
- (void)dealloc
{
    NSLog(@"Object Destoryed!");
#if !__has_feature(objc_arc)
    [super dealloc];
#endif
}
- (void)setExecuteFinished:(executeFinishedBlock)block
{
    OBJC_RELEASE(finishblock);
    finishblock = OBJC_COPY(block); //在非ARC下这里不能使用retain
}
- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block
{
    OBJC_RELEASE(finishblockparam);
    finishblockparam = OBJC_COPY(block); //在非ARC下这里不能使用retain
}
- (void)executeTest
{
    [self performSelector:@selector(executeCallBack) withObject:nil afterDelay:5];
}
- (void)executeCallBack
{
    _resultCode = 200;
    
    if (finishblock)
    {
        finishblock();
    }
    
    if (finishblockparam)
    {
        finishblockparam(self);
    }
}
@end
在非ARC环境下
执行下在语句的测试:
- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
    
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    
    [demo executeTest];
     
}2014-07-24 19:08:04.852 blockDemo[25104:60b] Object Constructor! 2014-07-24 19:08:09.854 blockDemo[25104:60b] call back ok.
__block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];在非ARC下,只虽一个__block关键词就可以。相对还是简单的。
好下面再来看一下在ARC模式下的block循环引用又是怎么样的。
在ARC模式下
执行下面语句:
- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [[BlockDemo alloc]init];
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    
    [demo executeTest];
     
}2014-07-24 19:20:33.997 blockDemo[25215:60b] Object Constructor! 2014-07-24 19:20:39.000 blockDemo[25215:60b] call back ok.同样会被引入循环。
相信看到这里的人,大多都要喷了,这哪个不知道呀,还知道怎么解决呢,非ARC中加了个__block,当然的在ARC中加一个__weak就搞定了。嗯,确实是这样,但别急,接着往下看,绝对有收获。在这里先自己默认想一下,你是如何加这个__weak的。
对于第一个问是点block 的循环引用(retain cycle)到这里暂告结束。下面讲第二点。因为block告警在非ARC 中暂未发现因写法引入(如果你知道,麻烦告诉我怎么弄产生告警,我好研究一下。)
下面讲在ARC模式下去除因写法产生的告警时需要注意的问题。
像上面的写法其实在ARC中会产生(Capturing ‘demo‘ strongly in this block is likely to lead to a retain cycle)告警。如下图:
在ARC中,编译器智能化了,直接提示这样写会产生循环引用。因此很多爱去除告警的朋友就会想法去掉,好,咱再来看去掉时需注意的问题。
情况一:
- (IBAction)onTest:(id)sender
{
    __weak BlockDemo *demo = [[BlockDemo alloc]init];
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    [demo executeTest];
}直接在前面加一个__weak,但这样真的没有告警了吗?如果有,哪么恭喜欢你,说明编译器还帮你大忙。见下图这时还会告警,说这是一个WEAK变量,就马上会被release。因此就不会执行block中的内容。大家可以运行一下看
输出结果为:
2014-07-24 19:38:02.453 blockDemo[25305:60b] Object Constructor! 2014-07-24 19:38:02.454 blockDemo[25305:60b] Object Destoryed!很显然,马上被release了,所以block 中的代码根本就不执行。
谢天谢地,幸好编译器提前告诉了我们有这个隐性危险。相信大家为解决告警,又会得到一个比较圆满的解决方案,见下:
- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [[BlockDemo alloc]init];
    
    __weak typeof(BlockDemo) *weakDemo = demo;
    
    [demo setExecuteFinished:^{
        if (weakDemo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    [demo executeTest];
}2014-07-24 19:40:33.204 blockDemo[25328:60b] Object Constructor! 2014-07-24 19:40:38.206 blockDemo[25328:60b] call back ok. 2014-07-24 19:40:38.207 blockDemo[25328:60b] Object Destoryed!
- (IBAction)onTest:(id)sender
{
    __weak BlockDemo *demo = [BlockDemo blockdemo]; //这里才是重点,前面是[[BlockDemo alloc]init];会有告警。
    
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    [demo executeTest];
}+ (BlockDemo *)blockdemo
{
    return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
}
不同点见下图:真心看不到作何告警,是不是。但这存在什么风险,风险就是运行的时候,block根本就没有run。因为对象早就释放了。
直接输出:
2014-07-24 19:47:53.033 blockDemo[25395:60b] Object Constructor! 2014-07-24 19:47:53.035 blockDemo[25395:60b] Object Destoryed!
好,到了尾声,来说说为什么朋友问我block会不会引行死循环,我说不会的理由。
见码:
- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init];
    
    [demo setExecuteFinishedParam:^(BlockDemo * ademo) {
        if (ademo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    
    [demo executeTest];
}由于我一直都这样写block,所以朋友一问起,我就说不会循环引用了,因为压根他碰到的就是前面讲述的哪种访问方式,而我回答的是我的这种使用方式。正因为口头描述,与实际回复真是差之千里。。。哈哈。为了验证我朋友的这个,我特意写了个这篇文章,希望对大家有所帮助。最后,谢谢大家花时间阅读。
写给喜欢用Block的朋友(ios Block),布布扣,bubuko.com
原文地址:http://blog.csdn.net/fengsh998/article/details/38090205