Block memory
block 的内存管理,应该是最头疼的地方,就用这个来自WWDC的例子来解释一下吧。
当程序运行到这里时,stack 空间中有 shared 变量和 captured 变量。
这里可以看出,__block 变量开始是处于stack上的。
当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1。
这里可以看出,block 类型的变量开始时也是处在stack上的。
当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1。
这里值得注意的就是当我们直接修改stack 上的captured变量时,block1中的captured变量仍然是原来的数值10。事实上,从const 我们就可以看出,block1中的captured变量是不能被修改的而且是从stack原有变量的一个const 拷贝。在block1中访问的captured变量是const拷贝的,也就是说block1中captured = 10,而不是原有的stack上的值 20。当然,在block1中,我们也不能修改captured变量。
Copy block
block在一开始是处在stack上的,这是为了考虑到效率的原因,但是,有时候是需要block的生命周期长于一开始的stack,这时,我们就通过copy block 来将block复制到heap。
当程序执行完 block2 = [block1 copy];时,__block 类型变量shared,被复制到了heap中,很显然,shared变量需要被block和block2共享(当然还有stack也要共享),而block2被移动到heap中,很可能生命周期会长于stack,所以,shared也被复制到了heap中。而block2中的captured 也被复制到了heap中。
当程序执行完 block3 = [block2 copy];时, 我们看到的是,block2 和block3 其实指向的是同一片内存空间。事实上,block的数据结构中,保存了引用计数,而对于copy到heap中的block 再copy时,行为同普通对象retain一样,会使引用计数+1。那么如果我们对[block retain]会如何呢? 实际上什么都没有发生,至少在现在的runtime版本下。因为retain中,不仅有引用计数+1在,而且retain的返回值,必须同返回调用对象的地址一样,而block的地址是可能变化的(stack or heap),所以,这里retain的行为几乎是被忽略掉的。
当heap中的block变量先于stack被销毁时,如调用 [block2 release]; [block3 release];,heap中的block2,block3 由于引用计数为0 而被销毁,而 __block 变量shared则还在heap中,因为stack还要使用,block1 也要使用。
当heap中的block变量晚于stack时,显然,stack 被清除,function中也啥都没了。
最后,当block2 和block3 都被release之后。则恢复到最初状态
block details
当我们写出一个Block literal expression
^ { printf("hello world\n"); }
事实上,编译器为我们生成了如下结构
struct __block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(struct __block_literal_1 *);
struct __block_descriptor_1 *descriptor;
};void __block_invoke_1(struct __block_literal_1 *_block) {
printf("hello world\n");
}static struct __block_descriptor_1 {
unsigned long int reserved;
unsigned long int Block_size;
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1)}; 当Block literal expression 使用时 __block_literal_1 则会被初始化为:struct __block_literal_1 _block_literal = {
&_NSConcreteStackBlock,
(1<<29), <uninitialized>,
__block_invoke_1,
&__block_descriptor_1
};
下一个例子
int x = 10;
void (^vv)(void) = ^{printf("x is %d\n", x);};
x = 11;
vv();
编译器会生成如下结构
struct __block_literal_2{
void *isa;
int flags;
int reserved;
void (*invoke)(struct __block_literal_2 *);
struct __block_descriptor_2 *descriptor;
const int x;
};
void __block_invoke_2(struct __block_literal_2 *_block){
printf("x is %d\n", _block->x);
}
void struct __block_descriptor_2{
unsigned long int reserved;
unsigned long int block_size;
}__block_descriptor_2 = {0, sizeof(struct __block_literal_2)};
struct __block_literal_2 __block_literal_2 = {
&NSConcreteStackBlock,
(1<<29),
__block_invoke_2,
&__block_descriptor_2,
x
};
block中使用的普通变量(int, char *)导入是const copy。普通对象则会retain。__block 类型变量则什么不做,只是保存一个指针,全局变量也只是保存一个简单的指针。
当然,block 可能也会嵌套block,那么又会是什么样子?其实不复杂,复杂的只是增加了复制函数,和释放函数,这一点很像C++的拷贝构造函数,在必要时生成。
void (^existingBlock)(void) = …;
void (^vv)(void) = ^{existingBlock();};
vv();
struct __block_literal_3{
...;//esisting block
};
struct __block_literal_4{
void *isa;
int flags;
int reserved;
void (*invoke)(struct __block_literal_4 *);
struct __block_literal_3 *const existingBlock;
};
void __block_invoke_4(struct __block_literal_3 *__block) {
__block->existingBlock->invoke(__block->existingBlock);
}
void __block_copy_4(struct __block_literal_4 *dst, struct __block_literal_4 *src) {
//_Block_copy_assign(&dst->existingBlock, src->existingBlock, 0);
_Block_object_assign(&dst->existingBlock, src->existingBlock, BLOCK_FIELD_IS_BLOCK);
}
void __block_dispose_4(struct __block_literal_4 *src) {
// was _Block_destroy
_Block_object_dispose(src->existingBlock, BLOCK_FIELD_IS_BLOCK);
}
static struct __block_descriptor_4 {
unsigned long int reserved;
unsigned long int Block_size;
void (*copy_helper)(struct __block_literal_4 *dst, struct __block_literal_4 *src);
void (*dispose_helper)(struct __block_literal_4 *);
} __block_descriptor_4 = {
0,
sizeof(struct __block_literal_4),
__block_copy_4,
__block_dispose_4,
};
初始化
struct __block_literal_4 _block_literal = {
&_NSConcreteStackBlock,
(1<<25)|(1<<29), <uninitialized>
__block_invoke_4,
& __block_descriptor_4
existingBlock,
};
__block storage variables
__block 变量是一种很特殊的数据类型,有自己的特有的数据结构
struct _block_byref_xxxx {
void *isa;
struct _block_byref_xxxx *forwarding;
int flags; //refcount;
int size;
// helper functions called via Block_copy() and Block_release()
void (*byref_keep)(void *dst, void *src); //需要时被生成
void (*byref_dispose)(void *);//需要时被生成
typeof(marked_variable) marked_variable;
};
看看__block 类型变量的使用
int __block i = 10;
i = 11;
struct _block_byref_i {
void *isa;
struct _block_byref_i *forwarding;
int flags; //refcount;
int size;
int captured_i;
} i = { NULL, &i, 0, sizeof(struct _block_byref_i), 10 };
i.forwarding->captured_i = 11;
显然,当block中增加了__block 类型变量之后,嵌套block 的拷贝函数也会增加对__block 变量的复制。
__block void (voidBlock)(void) = blockA;
voidBlock = blockB;
struct _block_byref_voidBlock {
void *isa;
struct _block_byref_voidBlock *forwarding;
int flags; //refcount;
int size;
void (*byref_keep)(struct _block_byref_voidBlock *dst, struct _block_byref_voidBlock *src);
void (*byref_dispose)(struct _block_byref_voidBlock *);
void (^captured_voidBlock)(void);
};
void _block_byref_keep_helper(struct _block_byref_voidBlock *dst, struct _block_byref_voidBlock *src) {
//_Block_copy_assign(&dst->captured_voidBlock, src->captured_voidBlock, 0);
_Block_object_assign(&dst->captured_voidBlock, src->captured_voidBlock, BLOCK_FIELD_IS_BLOCK | BLOCK_BYREF_CALLER);
}
void _block_byref_dispose_helper(struct _block_byref_voidBlock *param) {
//_Block_destroy(param->captured_voidBlock, 0);
_Block_object_dispose(param->captured_voidBlock, BLOCK_FIELD_IS_BLOCK | BLOCK_BYREF_CALLER)}
struct _block_byref_voidBlock voidBlock = {( .forwarding=&voidBlock, .flags=(1<<25), .size=sizeof(struct _block_byref_voidBlock *),
.byref_keep=_block_byref_keep_helper, .byref_dispose=_block_byref_dispose_helper,
.captured_voidBlock=blockA )};
voidBlock.forwarding->captured_voidBlock = blockB;
block中,引入了__block 会是什么情况
int __block i = 2;
functioncall(^{ i = 10; });
struct _block_byref_i {
void *isa; // set to NULL
struct _block_byref_voidBlock *forwarding;
int flags; //refcount;
int size;
void (*byref_keep)(struct _block_byref_i *dst, struct _block_byref_i *src);
void (*byref_dispose)(struct _block_byref_i *);
int captured_i;
};
struct __block_literal_5 {
void *isa;
int flags;
int reserved;
void (*invoke)(struct __block_literal_5 *);
struct __block_descriptor_5 *descriptor;
struct _block_byref_i *i_holder;
};
void __block_invoke_5(struct __block_literal_5 *_block) {
_block->i_holder->forwarding->captured_i = 10;
}
void __block_copy_5(struct __block_literal_5 *dst, struct __block_literal_5 *src) {
_Block_object_assign(&dst->i_holder, src->i_holder, BLOCK_FIELD_IS_BYREF | BLOCK_BYREF_CALLER);
}
void __block_dispose_5(struct __block_literal_5 *src) {
_Block_object_dispose(src->i_holder, BLOCK_FIELD_IS_BYREF | BLOCK_BYREF_CALLER);
}
static struct __block_descriptor_5 {
unsigned long int reserved;
unsigned long int Block_size;
void (*copy_helper)(struct __block_literal_5 *dst, struct __block_literal_5 *src);
void (*dispose_helper)(struct __block_literal_5 *);
} __block_descriptor_5 = { 0, sizeof(struct __block_literal_5) __block_copy_5, __block_dispose_5 };
struct _block_byref_i i = {( .forwarding=&i, .flags=0, .size=sizeof(struct _block_byref_i) )};
struct __block_literal_5 _block_literal = {
&_NSConcreteStackBlock,
(1<<25)|(1<<29), <uninitialized>,
__block_invoke_5,
&__block_descriptor_5,
2,
};
block 中的太多细节这里不做赘述,有兴趣的可以参考Block—ABI-Apple,也可以直接这里去看。
例子1
1
2
3
4
5
6
|
void
exampleA() {
char
a =
'A'
;
^{
printf(
"%c\n"
, a);
}();
}
|
这个例子:
A.始终能够正常运行 B.只有在使用ARC的情况下才能正常运行
C.不使用ARC才能正常运行 D.永远无法正常运行
例子2:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void
exampleB_addBlockToArray(
NSMutableArray
*array) {
char
b =
'B'
;
[array addObject:^{
printf(
"%c\n"
, b);
}];
}
void
exampleB() {
NSMutableArray
*array = [
NSMutableArray
array];
exampleB_addBlockToArray(array);
void
(^block)() = [array objectAtIndex:0];
block();
}
|
A.始终能够正常运行 B.只有在使用ARC的情况下才能正常运行
C.不使用ARC才能正常运行 D.永远无法正常运行
例子3
1
2
3
4
5
6
7
8
9
10
11
12
|
void
exampleC_addBlockToArray(
NSMutableArray
*array) {
[array addObject:^{
printf(
"C\n"
);
}];
}
void
exampleC() {
NSMutableArray
*array = [
NSMutableArray
array];
exampleC_addBlockToArray(array);
void
(^block)() = [array objectAtIndex:0];
block();
}
|
A.始终能够正常运行 B.只有在使用ARC的情况下才能正常运行
C.不使用ARC才能正常运行 D.永远无法正常运行
例子4
1
2
3
4
5
6
7
8
9
10
11
12
|
typedef
void
(^dBlock)();
dBlock exampleD_getBlock() {
char
d =
'D'
;
return
^{
printf(
"%c\n"
, d);
};
}
void
exampleD() {
exampleD_getBlock()();
}
|
A.始终能够正常运行 B.只有在使用ARC的情况下才能正常运行
C.不使用ARC才能正常运行 D.永远无法正常运行
例子5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
typedef
void
(^eBlock)();
eBlock exampleE_getBlock() {
char
e =
'E'
;
void
(^block)() = ^{
printf(
"%c\n"
, e);
};
return
<u><font color=
"\"red\""
>block</font></u>;
}
void
exampleE() {
eBlock <u><font color=
"\"red\""
>block</font></u> = exampleE_getBlock();
block();
}
|
A.始终能够正常运行 B.只有在使用ARC的情况下才能正常运行
C.不使用ARC才能正常运行 D.永远无法正常运行
答案会在回复之后出现~
本部分设定了隐藏,您已回复过了,以下是隐藏的内容
答案
例子1
A正确。这个例子可以正常运行。储存exampleA的栈只有在 block停止执行之后才会释放,因此,无论此 Block由系统分配到栈中还是我们自己手动分配到堆中,它都可以正常执行。
例子2
B正确。如果不使用ARC,这个 block是一个NSStackBlock,分配给exampleB_addBlockToArray的栈上。而当它在exampleB中执行的时候,由于栈被清空, block不再有效。
而使用ARC的话, block会分配到堆中,作为一个自动释放的NSMallocBlock
例子3
A正确
由于 block在自己的环路中不会抓取任何变量,它不需要在在运行的时候设置state,它会作为一个NSGlobalBlock编译。它既不是栈也不是堆,而是代码片段的一部分。所以它始终都能正常运行。
例子4
B正确。这个例子和例子2类似。如果不使用ARC, block会在exampleD_getBlock的栈上创建起来。然后当功能返回的时候会立即失效。
然而,以这个例子来说,这个错误非常明显,所以编译器进行编译会失败,错误提示是:error: returning block that lives on the local stack(错误,返回的 block位于本地的栈)。
例子5
B正确。这个例子和例子4类似,除了编译器没有认出有错误,所以代码会进行编译然后崩溃。更糟糕的是,这个例子比较特别,如果你关闭了优化,则可以正常运行。所以在测试的时候需要注意。
而如果使用ARC的话, block则会正确的位于堆上,作为一个自动释放的NSMAllocBlock。
结论
这套小测试有什么意义呢?意义就是要一直使用ARC。使用ARC, block大部分情况下都可以正常运行。
如果不使用ARC,谨慎起见,可以 block = [[block copy] autorelease],这样 block会比申明它的栈flame的有效期长。这样 block会被作为一个NSMAllocBlock强制复制到堆上。
但是,当然不会这么简单,根据苹果的文档,
Block只有当你在ARC模式下传递 block到栈上才会工作,比如说返回的时候。你不需要再次调用 Block Copy了。但是当 block从栈上传递到 arrayWithObjects: 和其他做了一个retain的方法是时,仍然需要使用[^{} copy]。
但是有一个LLVM的维护者之后也说过:
我们认为这是编译器的bug,它现在已经修复了。但是Xcode以后是否会在以后发布的新版本中解决这个问题,我也不知道。
所以,希望,苹果也把它认为是一个bug,在以后的新版本中会解决这个问题。让我们看看会怎么样吧。
原文: http://blog.parse.com/2013/02/05/objective-c-blocks-quiz/