iOS底层-Block系列一
Block的本质
简介
- 这项语言特性是作为”扩展”(extension)而加入GCC编译器中的,在近期版本的Clang中都可以使用.10.4版及以后的Mac OX X系统,与4.0版及及其后的iOS系统中,都含有正常执行块所需要的组件.从技术上讲,这是个位于C语言层面的特性,因此只要有支持此特性的编译器,以及能执行块的运行期组件,就可以在C/C++/Objective-C/Objective-C++代码中使用它.
- 苹果的编译器
- GCC/clang 都是开发Mac OX X与iOS程序所用的编译器
- GCC:XCode4,Mac OX X 10.4之前,用的是gcc编译器
- clang:XCode4开始用的是LLVM编译器(该编译器前端用的是clang编译器,编译器分前端跟后端,前端就是用来翻译的),Mac OX X 10.4之后
OC中的Block定义
- 在LLVM Block_private.h可以找到苹果关于block的相关定义。
-
仅仅看一些关键代码如下:
/* Revised new layout. */ struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };
- block的底层结构如右图所示
- 从上面可以推断
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- 编译分析
-
OC代码
int main(int argc, const char * argv[]) { @autoreleasepool { void (^myblock)(void) = ^{ NSLog(@"myblock"); }; myblock(); } return 0; }
-
通过编译后,取出核心代码如下:注意这里从C++角度分析!!!
//结构体__block_impl对象 struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; //C++中的对象,就是那个结构体的原型 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //构造函数,带有默认参数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; //block的类型 impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; //静态函数 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_t2_d94848jx1fq4vd6k8qcsfdq00000gn_T_main_448c61_mi_0); } //创建一个静态结构体变量__main_block_desc_0_DATA并初始化 static struct __main_block_desc_0 { size_t reserved;//保留字 size_t Block_size;//block的大小 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //main函数 int main(int argc, const char * argv[]) { { __AtAutoreleasePool __autoreleasepool; //因此block本质是一个__main_block_impl_0结构体对象指针 void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //函数调用:把myblock转化成__block_impl* 类型,取出FuncPtr成员函数,然后调用 ((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock); } return 0; }
- block本质就是一个
__main_block_impl_0
类型的结构体对象指针 -
疑问难点:
((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);
myblock
表面上是一个函数指针,但本质上是一个__main_block_impl_0
结构体对象类型的指针- 为何可以强制转换成
__block_impl
对象指针,而且可以直接从里面取出成员FuncPtr
呢? - 原因分析:
- ` __main_block_impl_0
对象内部的第一个成员是
__block_impl的impl`,根据对象在内存中存储的本质,对象的地址 == 第一个成员变量的地址。 - 因此
__main_block_impl_0
对象的地址,就是他的第一个成员变量的地址,因此可以直接强制转换,而且去取成员变量不会报错。
- ` __main_block_impl_0
- block本质就是一个
-
block的变量捕获(capture)
- 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
- 局部变量
- 自动变量(auto):会捕获到block内部,但是捕获到的仅仅是变量的值,值传递
- 自动变量的内存随时会销毁,所以不能捕获地址!!!
- 静态变量(static):会捕获到block内部,但是捕获到的是变量的地址,指针传递
- 自动变量(auto):会捕获到block内部,但是捕获到的仅仅是变量的值,值传递
- 全局变量
- 不会捕获到block内部,使用时直接可以访问。
- 局部变量
- 举例论证:
-
OC代码:
//全局变量 int g_var = 9; int main(int argc, const char * argv[]) { @autoreleasepool { //自动变量 auto int age = 10; //static变量 static int height = 20; void (^myblock)(void) = ^{ //将外部的age捕获进来 NSLog(@"====%d===%d====%d",age,height,g_var); }; age = 20; height = 30; g_var = 10; //打印 ====10===30====10 myblock(); } return 0; }
-
编译成C++,核心代码
int g_var = 9; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //age成员,值 int age; //height成员,注意这里是指针!!! int *height; //构造函数,带有初始化列表,默认参数 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) { impl.isa = &_NSConcreteStackBlock; //block类型 impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { //值传递 int age = __cself->age; // bound by copy //指针传递 int *height = __cself->height; // bound by copy //__cself取值age 、 *height //g_var直接取值 NSLog((NSString *)&__NSConstantStringImpl__var_folders_t2_d94848jx1fq4vd6k8qcsfdq00000gn_T_main_f0817d_mi_0,age,(*height),g_var); } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10; static int height = 20; void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height)); age = 20; height = 30; g_var = 10; ((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock); } return 0; }
-
-
self的捕获
#import "Person.h" @implementation Person //这么会捕获self吗? //会的,因为当前的test函数,本质是objc_msgSend(self,@selector(test));,self作为局部变量传递到函数中去的,因此self是局部变量,而且是指针类型 -(void)test{ void(^block)(void) = ^{ NSLog(@"====%@",self->_name); }; block(); } @end
block的三种类型
- 常见的变量有哪些?
- 在C语言中常见的变量如下:
- 自动变量(Auto),也可以称为局部变量
- 函数参数(形参)
- 静态变量(局部变量用static修饰)
- 静态全局变量(全局变量用static修饰)
- 全局变量
- 而在OC中常用的,变量除了函数参数以外,其他都用到了
- 在C语言中常见的变量如下:
- block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- 全局block:
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
- 栈block:
__NSStackBlock__ ( _NSConcreteStackBlock )
- 堆block:
__NSMallocBlock__ ( _NSConcreteMallocBlock )
- 全局block:
-
block的继承体系
void test(){ void(^block)(void) = ^{ NSLog(@"=======block="); }; //既然block也是一个OC对象,那么我们可以查看他的父类 NSLog(@"=======block=%@===",[block class]); NSLog(@"=======block=%@===",[block superclass]); NSLog(@"=======block=%@===",[[block superclass] superclass]); NSLog(@"=======block=%@===",[[[block superclass] superclass] superclass]); }
-
打印:
=======block=__NSGlobalBlock__=== =======block=__NSGlobalBlock=== =======block=NSBlock=== =======block=NSObject===
-
继承体系:
__NSGlobalBlock__ :__NSGlobalBlock:NSBlock:NSObject
-
-
三种block的特点:
- _NSConcreteStackBlock
- 特点:存放在栈区,生命周期由系统控制的,一旦返回之后,就被系统销毁了。
- 栈block的表现形式:
- 条件1:拥有局部变量(自动变量)或者成员属性变量(即使被strong或者copy修饰的成员变量)
- 没有被强引用
- 如下面的例1:
- _NSConcreteGlobalBlock
- 特点:这种Block存储在程序的数据区域(跟函数存储在一起),生命周期从创建到应用程序结束。全局block的copy是个空操作,实际上就相当于单粒.
- 全局block的表现形式:
- block中没有用到任何block内部以外的变量
- block内部仅仅用到了全局变量/静态全局变量,静态变量
- 如下面的例2
- _NSConcreteMallocBlock
- 特点:存放在堆区,没有强指针引用即销毁,生命周期由程序员控制
- 堆block的表现形式:
- 堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)
- 在ARC环境下,以下几种情况,编译器会自动的判断,把Block自动的从栈copy到堆
- 手动调用copy的栈block
- 栈Block被强引用,被赋值给__strong或者id类型
- copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,这可以归纳为上一条
- Block是函数的返回值
- 调用系统API入参中含有usingBlcok的方法
- 举例3:
-
例1:stackBlock
#import "TestObject.h" @interface TestObject () { int b; } @property (nonatomic,copy) NSString *str; @end @implementation TestObject - (void)test { int i = 10; //注意,这个block可是srong类型,OC中定义一个变量默认情况下是__strong类型 //堆block // void (^block)() = ^{i;}; //栈block __weak void (^weakBlock)() = ^{i;}; // 创建时,都会在栈中 NSLog(@"%@", ^{i;}); // 获取了该对象的成员属性变量 NSLog(@"%@", ^{b;}); //被copy修饰的str NSLog(@"=====%@", ^{ _str;} ); // 如果是weak类型的block NSLog(@"%@", weakBlock); } @end
-
打印结果如下:
<__NSStackBlock__: 0x7fff5b3600f0> <__NSStackBlock__: 0x7fff5b3600b8> <__NSStackBlock__: 0x7fff5b360118> =====<__NSStackBlock__: 0x7fff5594c0a8>
-
-
例2:两种globalBlock
#import <Foundation/Foundation.h> int global_i = 1; static int static_global_j = 2; int main(int argc, const char * argv[]) { static int static_k = 3; //globalBlock1:类型1 void (^myBlock)(void) = ^{ NSLog(@"Block中 变量 = %d %d %d",static_global_j ,static_k, global_i); }; myBlock(); NSLog(@"myBlock===%@",myBlock); //globalBlock2:类型2 void (^block2)(void) = ^{ }; NSLog(@"block2===%@",block2); return 0; }
-
打印结果:
Block中 变量 = 2 3 1 myBlock===<__NSGlobalBlock__: 0x10cc6b050> block2===<__NSGlobalBlock__: 0x10cc6b090>
-
-
堆block
#import "TestObject.h" @interface TestObject () { int b; } @property (nonatomic,copy) void (^tesblock)(); @property (nonatomic,copy) NSString *str; @end @implementation TestObject - (void)test { int i = 10; // 1.手动调用copy的栈block NSLog(@"%@", [^{i;} copy]); //注意,这个block可是srong类型,OC中定义一个变量默认情况下是__strong类型 //2.栈Block被强引用,被赋值给__strong或者id类型 void (^block)() = ^{i;}; NSLog(@"block===%@", block); //3.copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock self.tesblock = ^{ //如果没有这个i,那就是全局block,即使用copy引用,也是全局block,从而也说明全局block是不可囊转化成堆block i; }; NSLog(@"tesblock===%@", self.tesblock); //4.Block是函数的返回值 NSLog(@"block3===%@", [self test3]); //5.调用系统API入参中含有usingBlcok的方法(这个还不懂,以后有空在怼吧) } - (void(^)())test3 { //如果是全局block那么,即使是函数的返回值,他仍然还是全局block //return ^{}; int a; return ^{a;}; } @end
-
打印结果:
<__NSMallocBlock__: 0x6000002425b0> block===<__NSMallocBlock__: 0x60400025c1a0> tesblock===<__NSMallocBlock__: 0x60400025c290> block3===<__NSMallocBlock__: 0x60400025c3b0>
-
- _NSConcreteStackBlock
block作为函数的参数
-
当Block为函数参数的时候,需要我们手动copy一份到堆上
- (void)test { int i = 10; [self test4:^{i;}]; } - (void)test4:(void (^)())sblock { //sblock是一个栈block,需要copy操作 NSLog(@"sblock===%@", [sblock copy]); }