block.c:
struct Block_layout {
void *isa;//指向所属类的指针,也就是block的类型 NSStackBlock NSGlobalBlock NSMallocBlock
int flags;//标志变量,在实现block的内部操作时会用到
int reserved;//保留变量
void (*invoke)(void *, ...);//执行时调用的函数指针,block内部的执行代码都在这个函数中
struct Block_descriptor *descriptor;//block的详细描述,包含 copy/dispose 函数,处理block引用外部变量时使用
/* Imported variables. */
//variables: block范围外的变量,如果block没有调用任何外部变量,该变量就不存在
};
struct Block_descriptor {
unsigned long int reserved;//保留变量
unsigned long int size;//block的内存大小
void (*copy)(void *dst, void *src);// 拷贝block中被 __block 修饰的外部变量
void (*dispose)(void *);//和 copy 方法配置应用,用来释放资源
};
block类型
NSStackBlock 存储于栈区
NSGlobalBlock 存储于程序数据区
NSMallocBlock 存储于堆区
NSGlobalBlock
block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。引用static也为globalBlock
NSStackBlock
block 内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。MRC 环境下,[[mutableAarry addObject: blockA],(在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在其所在作用域结束也就是函数出栈后,从mutableAarry中取到的blockA已经被回收,变成了野指针。正确的做法是先将blockA copy到堆上,然后加入数组。支持copy,copy之后生成新的NSMallocBlock类型对象。
NSMallocBlock
如上例中的_block,[blockA copy]操作后变量类型变为 NSMallocBlock,支持retain、release,虽然 retainCount 始终是 1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放(可多次retain、release 操作验证)。copy之后不会生成新的对象,只是增加了一次引用,类似retain,尽量不要对Block使用retain操作。
在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 NSStackBlock 类型的 block 转换为 NSMallocBlock 类型
clang前后
原代码
-(void)blockDemo{
void (^block)(void) = ^{
};
}
clang后:
struct __block_impl {
void *isa;//指向所属类的指针,也就是block的类型
int Flags; //标志变量,在实现block的内部操作时会用到
int Reserved; //保留变量
void *FuncPtr;//block执行时调用的函数指针
};
//block内部实现 func0
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
}
static struct __ViewController__blockDemo_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__ViewController__blockDemo_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockDemo_block_impl_0)};
static void _I_ViewController_blockDemo(ViewController * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__ViewController__blockDemo_block_impl_0((void *)__ViewController__blockDemo_block_func_0, &__ViewController__blockDemo_block_desc_0_DATA));
}
捕获变量 __block
-(void)blockDemo{
__block int a = 100;
void (^block)(void) = ^{
a++;
};
block();
}
clang后
struct __Block_byref_a_0 {
void *__isa; //指向所属类的指针,被初始化为 (void*)0
__Block_byref_a_0 *__forwarding;//指向对象在堆中的拷贝
int __flags;//标志变量,在实现block的内部操作时会用到
int __size;//对象的内存大小
int a;//原始类型的变量
};
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
}
可以看到 多了一个结构体 被__block修饰的变量被封装成了一个对象,类型为__Block_byref_a_0,然后把&a作为参数传给了block。
其中,isa、__flags 和 __size 的含义和之前类似,而 __forwarding 是用来指向对象在堆中的拷贝,runtime.c 里有源码说明:
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
...
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
// 堆中拷贝的forwarding指向它自己
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
// 栈中的forwarding指向堆中的拷贝
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
...
}
这样做是为了保证在 block内 或 block 变量后面对变量a的访问,都是直接访问堆内的对象,而不上栈上的变量。同时,在 block 拷贝到堆内时,它所捕获的由 __block 修饰的局部基本类型也会被拷贝到堆内(拷贝的是封装后的对象),从而会有 copy 和 dispose处理函数。
block 用copy修饰
首先, block是一个对象, 所以block理论上是可以retain/release的. 但是block在创建的时候它的内存是默认是分配在栈(stack)上, 而不是堆(heap)上的. 所以它的作用域仅限创建时候的当前上下文(函数, 方法...), 当你在该作用域外调用该block时, 程序就会崩溃.
1.一般情况下你不需要自行调用copy或者retain一个block. 只有当你需要在block定义域以外的地方使用时才需要copy. Copy将block从内存栈区移到堆区.
2.其实block使用copy是MRC留下来的也算是一个传统吧, 在MRC下, 如上述, 在方法中的block创建在栈区, 使用copy就能把他放到堆区, 这样在作用域外调用该block程序就不会崩溃.
3.但在ARC下, 使用copy与strong其实都一样, 因为block的retain就是用copy来实现的.