我们针对不同情况来讨论block的存放位置:
以下情况中的block位于堆中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
void
foo()
{
__block
int
i =
1024
;
int
j =
1
;
void
(^blk)(
void
);
void
(^blkInHeap)(
void
);
blk = ^{ printf(
"%d, %d\n"
, i, j);};
//blk在栈里
blkInHeap = Block_copy(blk);
//blkInHeap在堆里
}
- (
void
) fooBar
{
_oi =
1
;
OBJ1* oj =
self
;
void
(^oblk)(
void
) = ^{ printf(
"%d\n"
, oj.oi);};
void
(^oblkInHeap)(
void
) = [oblk
copy
];
//oblkInHeap在堆中
}
|
以下情况中的block位于全局区:
1
2
3
4
5
6
7
8
9
10
|
static
int
(^maxIntBlock)(
int
,
int
) = ^(
int
a,
int
b){
return
a>b ? a:b;};
- (
void
)fooBar
{
int
(^maxIntBlockCopied)(
int
,
int
) =[maxIntBlock
copy
];
}
void
foo()
{
int
(^maxIntBlockCopied)(
int
,
int
) = Block_copy(maxIntBlock);
}
|
需要注意的是,这里复制过后的block依旧位于全局区,实际上,复制操作是直接返回了原block对象。
全局区的变量存储位置与block无关:
1
2
3
4
5
6
7
8
|
static
int
gVar =
0
;
//__block static int gMVar = 1;
void
foo()
{
static
int
stackVar =
0
;
// __block static int stackMVar = 0;
}
|
注意,static变量是不允许添加__block标记的
此时,你可能会问,当函数foo返回后,栈上的j已经回收,那么blkInHeap怎么能继续使用它?这是因为没有__block标记的变量,会被当做实参传入block的底层实现函数中,当block中的代码被执行时,j已经不是原来的j了,所谓物是人非就是这样吧~
另外,如果使用到变量j的所有block都没有被复制至heap,那么这个变量j也不会被复制至heap。
因此,即使将j++这一句放到blk()这句之前,这段代码执行后,控制台打印结果也是:1024, 1。而不是1024, 2
对block调用复制,有以下几种情况:
1.对全局区的block调用copy,会返回原指针,并且这期间不处理任何东西(至少目前的内部实现是这样);
2.对栈上的block调用copy,每次会返回新复制到堆上的block的指针,同时,所有__block变量都会被复制至堆一份(多次拷贝,只会生成一份)。
3.对已经位于heap上的block,再次调用copy,只会增加block的引用计数。
为什么我们不讨论retian的行为?原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。
objc类实例方法中的block如果被复制至heap,那么当前实例会被增加引用计数,当这个block被释放时,此实例会被减少引用计数。
但如果这个block没有使用当前实例的任何成员,那么当前实例不会被增加引用计数。这也是很自然的道理,我既然没有用到这个instance的任何东西,那么我干嘛要retian它?
我们要注意的一点是,我看到网上有很多人说block引起了实例与block之间的循环引用(retain-cycle),并且给出解决方案:不直接使用self而先将self赋值给一个临时变量,然后再使用这个临时变量。
但是,大家注意,我们一定要为这个临时变量增加__block标记(多谢第三篇文章回帖网友的提醒)。
这一章我们以结果导向的方式来说明了各种情况下,block的内存问题,下一章,我将剖析运行时库的源码,从根源阐述block的行为。也就是过程导向的方式了。