本文不讲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环境下
执行下在语句的测试:
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.
|
1
|
__block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
|
好下面再来看一下在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变量,就马上会被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!
|
谢天谢地,幸好编译器提前告诉了我们有这个隐性危险。相信大家为解决告警,又会得到一个比较圆满的解决方案,见下:
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];
}
|
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]);
}
|
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会不会引行死循环,我说不会的理由。
见码:
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];
}
|
由于我一直都这样写block,所以朋友一问起,我就说不会循环引用了,因为压根他碰到的就是前面讲述的哪种访问方式,而我回答的是我的这种使用方式。正因为口头描述,与实际回复真是差之千里。。。哈哈。为了验证我朋友的这个,我特意写了个这篇文章,希望对大家有所帮助。最后,谢谢大家花时间阅读。