zoukankan      html  css  js  c++  java
  • 写给喜欢用Block的朋友(ios Block)

    作者:fengsh998原文地址:http://blog.csdn.net/fengsh998/article/details/38090205转载请注明出处如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号fengsh998来支持我,谢谢!

     

    http://blog.csdn.net/fengsh998/article/details/38090205 

    本文不讲block如何声明及使用,只讲block在使用过程中暂时遇到及带来的隐性危险。

    主要基于两点进行演示:

    1.block 的循环引用(retain cycle)

    2.去除block产生的告警时,需注意问题。

    有一次,朋友问我当一个对象中的block块中的访问自己的属性会不会造成循环引用,我哈绰绰的就回了一句,不会。兄弟,看完这个,希望你能理解我为什么会说不会循环引用。别废话,演示开始。

    下面是我专们写了一个类来演示:

    头文件.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    //
    //  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
    </foundation>

    实现文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    //
    //  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 和非ARC中进行编译演示,所以我特意加了ARC预编译判断。主要是方便不要改动太多的代码来给大家演示。

    在非ARC环境下

    执行下在语句的测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (IBAction)onTest:(id)sender
    {
        BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
         
        [demo setExecuteFinished:^{
            if (demo.resultCode == 200) {
                NSLog(@"call back ok.");
            }
        }];
         
        [demo executeTest];
          
    }

    输出结果:
    1
    2
    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.

    很显然。尽管demo 是局部变量,并autorelease但可以看出自然至终并没有得到释放,这是因为block中使用了 block内进行访问了自身的resultCode属性。相信很多朋友也都会解决这种循环引用问题。就是在变量前面加个__block,就像这样。
    1
    __block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
    在非ARC下,只虽一个__block关键词就可以。相对还是简单的。

    好下面再来看一下在ARC模式下的block循环引用又是怎么样的。

    在ARC模式下

    执行下面语句:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - (IBAction)onTest:(id)sender
    {
        BlockDemo *demo = [[BlockDemo alloc]init];
        [demo setExecuteFinished:^{
            if (demo.resultCode == 200) {
                NSLog(@"call back ok.");
            }
        }];
         
        [demo executeTest];
          
    }

    执行输出结果:
    1
    2
    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中,编译器智能化了,直接提示这样写会产生循环引用。因此很多爱去除告警的朋友就会想法去掉,好,咱再来看去掉时需注意的问题。

    情况一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - (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中的内容。大家可以运行一下看

    输出结果为:

    1
    2
    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 中的代码根本就不执行。

    谢天谢地,幸好编译器提前告诉了我们有这个隐性危险。相信大家为解决告警,又会得到一个比较圆满的解决方案,见下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (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];
    }

    这样写,即去除了告警又保证了block的运行。这才是我们最终想要的结果。
    输出为:
    1
    2
    3
    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!

    但大家别得意。有提示,相信大家都能处理,并得到个好的解决方法。哪么下面大来再来看一下这个写法,让你真心甘拜下风。。。。。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - (IBAction)onTest:(id)sender
    {
        __weak BlockDemo *demo = [BlockDemo blockdemo]; //这里才是重点,前面是[[BlockDemo alloc]init];会有告警。
         
        [demo setExecuteFinished:^{
            if (demo.resultCode == 200) {
                NSLog(@"call back ok.");
            }
        }];
        [demo executeTest];
    }
    其实只是把init放到了类方法中进行书写而已,但会有什么不同。
    1
    2
    3
    4
    + (BlockDemo *)blockdemo
    {
        return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
    }
    不同点见下图:真心看不到作何告警,是不是。但这存在什么风险,风险就是运行的时候,block根本就没有run。因为对象早就释放了。


    直接输出:

    1
    2
    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但又想当然的朋友,有一些朋友喜欢去除告警,但只是盲目的加上__weak 或__block关键语,往往可能存在一些重大的安全隐患。就像演示中block根本不走。如果到了发布时,为了去告警而这样简单的处理了,并没有进行测试就打包。哪么将死得很惨。。。。。

    好,到了尾声,来说说为什么朋友问我block会不会引行死循环,我说不会的理由。

    见码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - (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];
    }

    不管是在外面init,还是在里面,且没有加__block 及__weak。为什么,因为我个人常常在使用自己写的block时,如果是回调,比较喜欢把自身当作参数传到block中。这样期实是编译器给我们做了弱引用。因此不会产生循环引用。

    由于我一直都这样写block,所以朋友一问起,我就说不会循环引用了,因为压根他碰到的就是前面讲述的哪种访问方式,而我回答的是我的这种使用方式。正因为口头描述,与实际回复真是差之千里。。。哈哈。为了验证我朋友的这个,我特意写了个这篇文章,希望对大家有所帮助。最后,谢谢大家花时间阅读

  • 相关阅读:
    路由和数据传递
    ASP.NET MVC3 自定义编辑模版
    最新Bootstrap手册
    ASP.NET MVC Bundles 用法和说明(打包javascript和css)
    MVC匿名类传值学习
    .net通用类型转换方法
    C#.net XML的序列化与反序列化
    The Connection Strings Reference
    ASP.NET MVC使用AuthenticationAttribute验证登录
    ASP.NET MVC Bundles 之学习笔记
  • 原文地址:https://www.cnblogs.com/apem/p/5009361.html
Copyright © 2011-2022 走看看