底层原理之Block

iOS 面试集合之block #### block 1. 本质:block是个结构体对象,封装了函数调用 ```objective-c // 底层的源码 struct __main_block_impl_0{ struct __block_impl impl; struct __main_block_desc_0* Desc; } struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;// 函数集合 }; struct __main_block_desc_0 { size_t reserved; size_t Block_size; } ``` ```objective-c // 一个简单的block与源码执行过程分析 int main(int argc, const char * argv[]) { @autoreleasepool { void(^block)(void) = ^{ NSLog(@"Hello world!"); }; block(); } return 0; } int main(int argc, const char * gv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // 定义block对象 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); // 执行block对象1 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } /* main函数里面的简化过程 int main(int argc, const char * gv[]) { { __AtAutoreleasePool __autoreleasepool; // 定义block对象 通过__main_block_impl_0函数传入两个参数,返回一个对象的地址给*block,即block是对象 *block = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA) ); // 执行block对象1 block->FuncPtr(block); } return 0; } */ // __main_block_impl_0函数 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; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // 上面函数的第一个参数:封装了block执行逻辑的函数6y static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_jy_5s1r4rrn26v05hfgpdjvx0v80000gn_T_main_4ef11d_mi_0); } // 第二个参数:计算结构体大小 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)}; // 默认赋值 ``` 2. 变量捕获(捕获到block内部) + 局部变量可以捕获到 + auto类型的是通过值传递方式; + static类型的是通过指针传递方式; + 全局变量捕获不到,但是可以直接访问 ```objective-c // 全局变量 int total_a = 1; static int total_b = 2; int main(int argc, const char * argv[]) { @autoreleasepool { auto int age = 10; static int height = 20; void(^block)(void) = ^{ NSLog(@"age = %d, height = %d, total_a = %d, total_b = %d", age, height, total_a, total_b); }; total_a = 10; total_b = 20; age = 100; height = 200; block(); // age = 10, height = 200, total_a = 10, total_b = 20 } return 0; } ``` 全局变量,局部变量:auto类型和static类型不一致处理方法源码分析: ```objective-c int total_a = 1; static int total_b = 2; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; // 局部auto变量在block中,表示捕获到block中 int *height; // 局部static变量在block中,也捕获到了;但是指针传递的方式 // 全局变量未在其中,不会捕获到block中 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {// height(_height) c++语言:height = _height impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // 封装好的block函数调用 全局变量不用传递 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 NSLog((NSString *)&__NSConstantStringImpl__var_folders_jy_5s1r4rrn26v05hfgpdjvx0v80000gn_T_main_153472_mi_0, age, (*height), total_a, total_b); } 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; auto int age = 10; static int height = 20; void(*block)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, age, &height // static通过传址的方式传递 ); total_a = 10; total_b = 20; age = 100; height = 200; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } ``` 3. block的类型 + _ _NSGlobalBlock_ _ : 没有访问auto变量;存储在数据段 + _ _NSStackBlock_ _: 访问了auto变量; 存储在栈上,随时会回收,在MRC环境下block默认是没有copy的 + _ _NSMallocBlock_ _: stackBlock 调用了copy; 存储在堆上,在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上 + block作为函数返回值 + 将block赋值给__strong指针 + block作为cocoa api中方法名含有usingBlock的方法参数时,比如:数据的enumerateObjectUsingBlock方法 + block作为GCD 参数时 4. 对象类型的auto变量 + 当block内部访问了对象类型的auto变量时 + 如果block是在栈上,将不会对auto变量产生强引用 + 如果block是在堆上 + 会调用block内部的copy函数 + copy函数内部会调用_Block_object_assign函数 + _Block_object_assign函数会根据auto变量的修饰符(_ _ _ strong, _ _ weak)做出相应的操作 + 如果block从堆上移除 + 会调用block内部的dispose函数 + dispose函数内部会调用_Block_object_dispose函数 + _Block_object_dispose函数会自动释放引用的auto变量 + copy函数:栈上的block复制到堆时调用 + dispose函数:堆上的block被废弃时 5. 修改变量 + __block 可以用于解决block内部无法修改auto变量值得问题(static和全局变量是可以修改的) + __block不能修饰全局变量和static变量 + 编译器会将__block变量包装成一个对象 **源码解释1**:block 内部无法修改自动(临时)变量的问题 ```objective-c int main(int argc, const char * argv[]) { @autoreleasepool { int age = 10; void(^block)(void) = ^{ // age = 20; NSLog(@" age = %d", age); }; block(); } return 0; } ``` 上面代码的源码:执行打印age的时候实际是在__main_block_func_0函数里,该处的age访问的是block的,但实际要修改的是main函数里的age,作用域不一致 ```objective-c static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy — —-- 该处age访问的是block的 NSLog((NSString *)&__NSConstantStringImpl__var_folders_jy_5s1r4rrn26v05hfgpdjvx0v80000gn_T_main_c6b857_mi_0, age); } 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; // --- 该age作用域是在main里 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } ``` **源码解释2**:__block修饰之后封装成对象,以及可以修改变量 ```objective-c int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; void(^block)(void) = ^{ age = 20; NSLog(@" age = %d", age); }; block(); } return 0; } ``` 上面代码源码: ```objective-c // 加了block之后将age封装成了对象 struct __Block_byref_age_0 { void *__isa; // 拥有isa指针表明是个对象 __Block_byref_age_0 *__forwarding; // forwarding的类型时该对象,表明指向自己 int __flags; int __size; int age; // 由对象持有 }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref // 未加__block是int age,加了之后的变化 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref (age->__forwarding->age) = 20; // 修改age,因为是对象,且自己持有age,所以可以修改 NSLog((NSString *)&__NSConstantStringImpl__var_folders_jy_5s1r4rrn26v05hfgpdjvx0v80000gn_T_main_a6443a_mi_0, (age->__forwarding->age)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; // main函数里的age也变化了 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } ``` main函数里代码的简写: ```objective-c int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; void(*block)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344 ); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } ``` 6. 循环引用 解决方法有三种: + __weak: 不会产生强引用,指向的对象销毁时,会自动让指针置为nil(ARC环境下用 weak,非ARC下无weak,采用下面两种) + __unsafe_unretained: 不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变(因为不安全,ARC下建议用weak) + __block:需要手动去销毁 导致循环引用的原因:对象(如Person)拥有block属性,表示该对象持有block对象,block也会在内部有Person对象(详细看之前的block源码,可分析出内存图),相互的持有,形成一个循环圈。 截屏2022-02-01 下午11.18.17 使用_ _weak,_ _unsafe_unretained之后的图解: 将block对对象的持有修改为弱引用,block不再持有对象所以对象可以销毁 ![截屏2022-02-01 下午11.23.28](/Users/zhangcaiwei/Desktop/截屏2022-02-01 下午11.23.28.png) 使用_ _ block解决问题:_ _ block修改的对象,会在内存里自动将对象再包装一层(__Block_byref_Person_0),包装一层里持有对象Person, 该Person对象指向自己的对象区域,且该对象又会持有block对象,形成一个三角形的循环区域(如图中中间); 解决办法(图左):在block里面手动的释放对象,让_ _ block不再持有对象,打破闭环 ![截屏2022-02-01 下午11.30.35](/Users/zhangcaiwei/Desktop/截屏2022-02-01 下午11.30.35.png)

你可能感兴趣的:(底层原理之Block)