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

写给喜欢用Block的朋友(ios Block)的更多相关文章

  1. (译)IOS block编程指南 1 介绍

    Introduction(介绍) Block objects are a C-level syntactic and runtime feature. They are similar to stan ...

  2. iOS Block界面反向传值

    在上篇博客 <iOS Block简介> 中,侧重解析了 iOS Block的概念等,本文将侧重于它们在开发中的应用. Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C ...

  3. iOS block的用法

    本章学习目标: 1. 了解何谓block. 2. 了解block的使用方法. Block 是iOS在4.0之后新增的程式语法,严格来说block的概念并不算是基础程式设计的范围,对初学者来说也不是很容 ...

  4. iOS开发:使用Block在两个界面之间传值(Block高级用法:Block传值)

    iOS开发:使用Block在两个界面之间传值(Block高级用法:Block传值)   使用Block的地方很多,其中传值只是其中的一小部分,下面介绍Block在两个界面之间的传值: 先说一下思想: ...

  5. iOS block 机制

    本文要将block的以下机制,并配合具体代码详细描述: block 与 外部变量 block 的存储域:栈块.堆块.全局块 定义 块与函数类似,只不过是直接定义在另一个函数里,和定义它的那个函数共享同 ...

  6. iOS - Block产生Memory Leaks循环引用导致的内存泄漏以及解决方案

    在ARC(自动引用技术)前,Objective-c都是手动来分配释放 释放 计数内存,其过程非常复杂. ARC技术推出后,貌似世界和平了很多,但是其实ARC并不等同于Java或者C#中的垃圾回收,AR ...

  7. iOS - Block的简单使用

    Block 的使用有两种: .独立Block .内联Block   <一>独立Block 使用方式   一.定义一个Block Object,并调用.   1.定义   // 定义一个Bl ...

  8. ios Block详细用法

    ios Block详细用法 ios4.0系统已开始支持block,在编程过程中,blocks被Obj-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行.Blocks可以作为函数参数或者函数的 ...

  9. iOS Block的本质(一)

    iOS Block的本质(一) 1.对block有一个基本的认识 block本质上也是一个oc对象,他内部也有一个isa指针.block是封装了函数调用以及函数调用环境的OC对象. 2.探寻block ...

随机推荐

  1. asp.net ListBox 移除操作的思路

    ArrayList arrRight = new ArrayList(); protected void ImageButton1_Click(object sender, ImageClickEve ...

  2. hbase shell command

    进入hbase shell console $HBASE_HOME/bin/hbase shell 如果有kerberos认证,需要事先使用相应的keytab进行一下认证(使用kinit命令),认证成 ...

  3. 将一个tabel加到另一个table

    DataTable rate = GetRate(str_catchType,); dt.Merge(rate);//将两个table合并

  4. Eclipse搭建Python开发环境+Python中文处理

    1.基本需求 1.Eclipse 集成开发环境下载 http://115.com/file/c2vz7io5    JDK6下载 http://115.com/file/c2vz7idq     2. ...

  5. C#----我对坐标系的理解和图形转动

    目录: 设置图形的旋转 设置坐标轴的反向 图形的旋转 参考一个文章:http://www.bccn.net/Article/kfyy/vc/jszl/200601/3008.html ; 目标:让Dr ...

  6. centos 7.0 安装vim

    用的最小化 安装 看看跟VI命令有何区别 后面会记录使用经验 [root@localhost conf]# yum -y install vim 已加载插件:fastestmirror base | ...

  7. OC-基本

    #import <Foundation/NSObjCRuntime.h> //import: //1,同#include一样, 拷贝文件内容 //2,可以自动防止文件的内容被重复拷贝 /* ...

  8. RabbitMQ service is already present - only updating service parameters

    如果你安装RabbitMQ不是那么一番顺利..那么你有可能会重装多次.. So..问题来了..重装时你执行   rabbitmq-service install  的时候..有可能就会报这个错了.. ...

  9. ngrok内网穿透利器在windws下的使用

    1.到官网下载windows版本:https://ngrok.com/download 2.解压,双击“ngrok.exe” 3.输入“ngrok http 80”,会随机给你分配域名.见下图. ng ...

  10. AndroidStudio-使用Translations Editor

    前言 如果你的App支持多语言,你需要正确的管理你的翻译字符串资源.Android Studio提供了翻译编辑器使更容易的查看和管理翻译资源. 关于翻译编辑器 翻译资源存储工程的多个目录下的多个XML ...