底层原理之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源码,可分析出内存图),相互的持有,形成一个循环圈。 <img src="/Users/zhangcaiwei/Desktop/截屏2022-02-01 下午11.18.17.png" alt="截屏2022-02-01 下午11.18.17" style="zoom: 33%;" /> 使用_ _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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容