目录
一 ,Block的本质
.m 文件代码如下
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(void) = ^{
NSLog(@"this is a block-%d",age);
};
age = 20;
block();
}
return 0;
}
使用终端命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 查看底层实现的文件。
结论如下:
- Block 内部也有一个isa指针
- 封装了函数调用以及函数调用环境内的OC对象
二,Block的变量捕获
- 局部变量:auto/static 修饰的局部变量,Block 都会将其捕获到block内部;auto修饰的局部变量,block 通过值传递的方式访问;static修饰的局部变量,block 通过指针传递的方式访问;
- 全局变量:block 不将其捕获,直接访问。
- 以下举例只是验证了Block 捕获的是局部变量,至于static的捕获,希望大家自己动手实现一下,去验证为何 static 声明的局部变量在block 调用之前修改其值,block 内部引用的其值也跟着变。
#import "XZPerson.h"
@implementation XZPerson
//block 会将 _name 捕获到block内部吗?
- (void)test{
void (^block)(void) = ^{
NSLog(@"-------------%p",self->_name);
};
block();
}
@end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
/*
1.使用终端命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XZPerson.m 生成c++文件
2.block 本质代码如下,可以看到,block 是通过捕获ZPerson *self,进而访问成员变量_name.
struct __XZPerson__test_block_impl_0 {
struct __block_impl impl;
struct __XZPerson__test_block_desc_0* Desc;
XZPerson *self;
__XZPerson__test_block_impl_0(void *fp, struct __XZPerson__test_block_desc_0 *desc, XZPerson *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3.通过cpp 文件可以看出,test的底层实现如下:
static void _I_XZPerson_test(XZPerson * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__XZPerson__test_block_impl_0((void *)__XZPerson__test_block_func_0, &__XZPerson__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
每个方法都有两个隐藏的参数 self(方法调用者),SEL _cmd(方法的实现),两个都是局部变量,block会捕获局部变量到其内部也可从此得出结论
*/
三,Block 的类型
上面提到block 是oc对象,既然是对象就能调用oc的class 方法,查看其元类对象以及父类对象
void (^block)(void) = ^{
NSLog(@"this is a block");
};
NSLog(@"block-1%@",[block class]); /*结果:__NSGlobalBlock__*/
NSLog(@"block-2%@",[[block class] superclass]); /*结果:__NSGlobalBlock*/
NSLog(@"block-3%@",[[[block class] superclass] superclass]); /*结果:NSBlock*/
NSLog(@"block-4%@",[[[[block class] superclass] superclass] superclass]); /*结果:NSObject*/
通过以下方法可知道block的类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block1)(void) = ^{
NSLog(@"this is one block");
};
int age =10;
void (^block2)(void) = ^{
NSLog(@"this is two block-%d",age);
};
NSLog(@"%@-%@-%@",[block1 class],[block2 class],[^{
NSLog(@"this is three block-%d",age);
} class]);
//__NSGlobalBlock__ - __NSMallocBlock__ - __NSStackBlock__
}
return 0;
}
这里大家也许有个疑问?使用终端命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 查看底层c++文件这三个block生成的block 类型都为同一种,_NSConcreteStackBlock?这个不用奇怪,我们一切以运行时为主。clang 只是LLVM编译器的一部分,也许LLVm在运行过程中做了一定的处理。导致运行的结果是三种不同类型的Block。
总结:block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
- __NSStackBlock__ ( _NSConcreteStackBlock )
- __NSMallocBlock__ ( _NSConcreteMallocBlock )
int main(int argc, const char * argv[]) {
@autoreleasepool {
//MRC下探索:
//没有访问auto变量的block 就是global类型的block;
//访问全局+静态的局部变量都是global类型的block
//访问auto的就是stack 类型的block,注意ARC下打印的是malloc类型,如果想要探索本质,记得吧ARC关闭哦。
void (^block1)(void) = ^{
NSLog(@"this is one block");
} ;
//__NSGlobalBlock__ 调用copy 还是__NSGlobalBlock__
int age =10;
void (^block2)(void) = ^{
NSLog(@"this is two block-%d",age);
} ;
//MRC:__NSStackBlock__调用copy 变成了__NSMallocBlock__即从栈区变到了堆区
NSLog(@"%@-%@",[block1 class],[block2 class]);
}
return 0;
}