Block的本质
简单实现一个block。
//简单实现一个Block
int a = 10;
void (^myBlock)(void) = ^(){
NSLog(@"this is block1 %d",a);
};
myBlock();
然后编译成C++文件会发现block的底层是一个结构体,结构体中有一个isa指针。
int a = 10;
static int b = 10;
// 注意这里auto变量直接传递的值,静态变量传递的是b的地址
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
// __main_block_impl_0结构体中第一个成员就是__block_impl类型的impl,所以impl的地址和结构体的地址是一样的,所以这里可以强转。
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int *b;
// 构造函数,类似于OC的init方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;// 这里有一个isa指针。
int Flags;
int Reserved;
void *FuncPtr;
};
// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bh_hkvbvghs2c9fsgc_806b2ck80000gn_T_main_319ab7_mi_0,a,(*b));
// 这里b是指针 *b是取具体的值。
}
// 结构体
static struct __main_block_desc_0 {
size_t reserved;// 0
size_t Block_size;// 代表Block这个结构体占用多大内存
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
所以Objective-C中Block的本质其实是一个对象,因为它和对象一样底层都是结构体,结构体中都有一个isa指针。
关于局部变量、局部静态变量、全局变量、全局静态变量的区别
C++变量根据定义位置的不同,具有不同的作用域,作用域可分为6种:全局作用域,局部作用域,语句作用域,类作用域,命名作用域和文件作用域。
- 从作用域看:
- 全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包括全局变量定义的源文件需要用extern关键字再次声明这个全局变量。
- 静态局部变量具有局部作用域。它只被初始化一次,自从第一次初始化直到程序与你新内阁结束都一直存在,他和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
- 局部变量也只有局部作用域,他是自动对象,他在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用结束后,变量就被撤销,其所占用的内存也被收回。
- 静态全局变量也具有全局作用域,他与全局变量的区别在于如果程序包含多个文件的话,他作用于定义它的文件里,不能作用到其他文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同的静态全局变量,他们也是不同的变量。
- 从分配内存空间看: 全局变量、静态局部变量、静态全局变量都在静态存储区分配空间,而局部变量在栈分配空间。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上没有什么不同。区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。
- 静态变量会被放在程序的静态数据存储区里,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是他与堆栈变量和堆变量的区别。
- 变量用static告知编译器,自己仅仅在变量的作用域范围内可见。这一点是他与全局变量的区别。 从以上分析可以看出,把局部变量改变为静态变量后是改变了他的存储方式,即改变了他的生存期。把全局变量改变为静态变量后是改变了他的作用域,限制了他的使用范围,因此static这个说明符在不同的地方起的作用是不同的。
以上部分引用自静态局部变量和全局变量的区别!
Block捕获变量
为了保证block内部能正常访问外部变量,block有个变量捕获机制。
- auto变量---值传递,捕获到block内部,block内部会新增一个成员来存储这个变量的值。
- 为什么auto变量捕获的是具体的值,是因为auto变量会自动释放,释放之后再访问会报坏内存的错误。
- static局部静态变量---指针传递,捕获到block内部,block内部会新增一个成员来存储一个指针来指向这个静态变量。
- 根据上面的引用内容我们知道局部静态变量的作用域是定义它的函数体内,所以我们不能直接访问局部静态变量。所以需要新增一个成员来存储静态变量的地址。
- 局部静态变量,会一直存在于内存中,我们可以通过指针访问。
- 全局变量---直接访问,不会捕获。
- 全局变量我们可以直接访问。 需要注意的一点是self是作为一个局部变量被捕获的。OC的方法被编译成C函数的时候会自动加上Person *self、SEL _cmd这两个参数。
Block的类型
OC中总共有三种类型的Block。
- _NSGlobalBlock
- 没有访问auto变量,保存在常量区,copy之后依然是_NSGlobalBlock。
- _NSStackBlock
- 访问了auto变量,保存在栈区,随时可能销毁,copy之后变成 _NSMallocBlock。
- _NSMallocBlock
- 对NSStackBlock进行了copy获得,保存在堆区,受引用计数控制,copy之后依然是_NSMallocBlock。
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,如下情况:
- block作为函数返回值时。
- block赋值给__strong指针时。
- block作为Cocoa API中方法名含有usingBlock的方法参数时。
- block作为GCD API的方法参数时。
Block引用对象类型的auto变量
当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会对对象进行强引用。即使是用__strong修饰变量。
- 如果将block拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、_unsafe_unretained)做出响应的操作,类似于retain,形成强弱引用。
- 如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会对auto变量做一次release操作 当block内部访问了对象类型的变量时,__main_block_desc_0结构体中对多出两个函数(dispose和copy)
Block修改变量
关于__block的作用:
- __block可以用于block内部无法修改auto变量的问题。
- __block不能修饰全局变量、静态变量(static)。
- 编译器会将__block变量包装成一个对象
typedef void(^MyBlock)(void);
MyBlock block;
__block int a = 10;
block = ^(){
a = 20;
NSLog(@"a = %d",a);
};
block();
//上边的代码编译后的C++代码为
MyBlock block;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/**********************************************************************************/
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref 这里block捕获的是编译器生成的结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bh_hkvbvghs2c9fsgc_806b2ck80000gn_T_main_3b8364_mi_0,(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
__Block修饰的对象类型的变量的内存管理
- 当__block变量在栈上时,不会对指向的对象产生强引用。
- 当__block对象copy到堆上时:
- 会调用__block变量内部的copy函数。
- copy函数内部会调用_Block_object_copy函数
- _Block_object_copy函数会根据所指向对象的修饰符(__strong、__weak、_unsafe_unretained)做出响应的操作,类似于retain,形成强弱引用。(注意:这里仅限于ARC时会retain,MRC时不会retain)
- 如果__block变量从堆上移除
- 会调用__block变量内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放指向的对象(做一次release)