古巷悠悠岁月深,青石老街印旧痕
今夜小楼听风雨,不见当年伞下人面试
Block做为iOS中老生常谈的问题,也是面试中面试官比较喜欢问的 一个问题 ,下面咱们经过源码查看block的底层实现原理函数
Block:将函数
及其上下文
组装起来的对象
spa
建立一个PHJBlock类指针
@implementation PHJBlock - (void)test { int a = 10; void (^ block)(void) = ^{ NSLog(@"%d", a); }; block(); } @end
查看编译后的C++源码code
编译: Clang -rewrite-objc PHJBlock.m对象
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) { int a = 10; void (* block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, a)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }
__PHJBlock__test_block_impl_0的内部实现图片
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; int a; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
__block_impl的内部实现ip
看到isa,证实block的本质就是一个对象
内存
struct __block_impl { void *isa; // 看到isa,证实block的本质就是一个对象 int Flags; int Reserved; void *FuncPtr; };
栈Block
堆Block
全局Blockcmd
int a = 10; // 堆block void(^block)(void) = ^{ NSLog(@"%d", a); }; block(); NSLog(@"%@", block); // 全局block void(^block1)(void) = ^{ }; block1(); NSLog(@"%@", block1); // 栈block NSLog(@"%@", ^{ NSLog(@"%d", a); });
打印查看block内存地址
局部变量
基本数据类型:截获其值 对象类型:对于对象类型的局部变量连同全部权修饰符一块儿截获
静态局部变量
以指针形式
全局变量
不截获
静态全局变量
不截获
不加修饰词修饰的变量
- (void)test { int a = 100; void(^block)(void) = ^{ printf("%d", a); }; block(); }
编译后的C++代码:捕获外部变量的值
static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) { // 捕获 a 的值 int a = __cself->a; // bound by copy printf("%d", a); }
加修饰词修饰的变量
- (void)test { __block int a = 100; void(^block)(void) = ^{ a ++; printf("%d", a); }; block(); }
编译后的C++代码:捕获外部变量的的地址
总结:这也就解释了为何咱们在外部变量前加上__block就能在block内部能够修改变量的值
static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) { // 捕获 a 的地址 __Block_byref_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) ++; printf("%d", (a->__forwarding->a)); }
发现变量a加上__block后变成了一个对象__forwarding指针
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; };
- (void)test { __unsafe_unretained id obj = nil; __strong NSObject *obj1 = nil; void(^block)(void) = ^{ NSLog(@"__unsafe_unretained类型变量:%@", obj); NSLog(@"__strong类型变量:%@", obj1); }; block(); }
编译后的C++代码:连同外部变量的修饰词一块儿捕获
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) { // 修饰词一块儿捕获 __attribute__((objc_ownership(none))) id obj = __null; __attribute__((objc_ownership(strong))) NSObject *obj1 = __null; void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, obj, obj1, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }
总结:这也就解释了为何咱们在外部对象前加上__weak就能在block内部使用的时候能够避免循环引用问题
- (void)test { static int a = 100; void(^block)(void) = ^{ NSLog(@"static类型变量a :%d", a); }; block(); }
编译后的C++代码:捕获变量的地址
&a
表明传入的是a变量的地址
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) { static int a = 100; // 下面的&a表明传入的是a变量的地址 void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, &a)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }
int *a
接收传入的a变量的地址
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; // 捕获a变量的地址 int *a; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
int a = 100; @implementation PHJBlock - (void)test { void(^block)(void) = ^{ NSLog(@"全局变量a :%d", a); }; block(); } @end
编译后的C++代码:不会捕获全局变量
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
static int a = 100; @implementation PHJBlock - (void)test { void(^block)(void) = ^{ NSLog(@"全局变量a :%d", a); }; block(); } @end
编译后的C++代码:不会捕获静态全局变量
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
须要加__block
__block NSMutableDictionary *dicM = nil; void(^block)(void) = ^{ dicM = [NSMutableDictionary dictionary]; }; block();
不须要加__block
NSMutableDictionary *dicM = nil; void(^block)(void) = ^{ [dicM setObject:@(1) forKey:@"1"]; }; block();
总结:赋值的时候须要加__block,操做使用的时候不用加
栈Block没有进行copy操做
栈__forwarding指针都指向栈中本身的变量
栈Block若是进行了copy操做
栈和堆上的__forwarding指针都指向堆的变量
总结:不论在任何内存位置,均可以顺利访问同一个__block变量
栈block进行copy,获得堆block 堆block进行copy,增长引用计数 全局block进行copy,block不会产生影响
MRC下,不会产生循环引用 在ARC下,会产生循环引用,引发内存泄漏,解决:在block内部对__block修饰的对象变量进行置nil操做