iOS经典面试题之深入分析block相关高频面试题
一、前言
- 本文重点来研究一下 objc 的 block,并具体来分析一下以下一些面试题目:
- block 的内部实现,结构体是什么样?
- block 是类吗?有哪些类型?
- 一个 int 变量被 __block 修饰与否的区别?block 的变量如何截获?
- block 在修改 NSMutableArray,需不需要添加 __block?
- block 怎么进行内存管理?
- block 可以用 strong 修饰吗?
- 解决循环引用时,为什么要用 __strong、__weak 修饰?
- block 发生 copy 时机是什么?
- block 访问对象类型的 auto 变量时,在 ARC 和 MRC 下有什么区别?
- 在回答这些问题之前,需要了解一些 block 背景相关的知识,如下:
- 如何查看 block 的内部实现,也就是说转换成背后真正的 c/c++ 代码的 block 是什么样的?
- block 的转换格式是什么?
- block 原理是什么?
- 关于 block 变量的作用域是什么?
二、Objective-C 转 C++ 的方法
- 现有一个 TestClass.m 类,其中 block 代码如下:
@interface TestClass ()
@end@implementation TestClass
- (void)testMethods {void (^blockA)(int a) = ^(int a) {NSLog(@"%d",a);};if (blockA) {blockA(1990);}
}
@end
- 打开终端,cd 到 TestClass.m 所在目录,使用如下命令:
clang -rewrite-objc TestClass.m
- 就会在当前文件夹内自动生成对应的 TestClass.cpp 文件。如果提示 clang 没有的话,则需要安装,输入如下命令:
brew install clang-format或者brew link clang-forma然后输入 下面命令 测试是否好使clang-format --help
- 经过上述转换操作,可以在 TestClass.cpp 中最下面发现如下 C++ 代码:
// @interface TestClass ()
/* @end */// @implementation TestClassstruct __TestClass__testMethods_block_impl_0 {struct __block_impl impl;struct __TestClass__testMethods_block_desc_0* Desc;__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__cself, int a) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_ee18d3_mi_0,a);}static struct __TestClass__testMethods_block_desc_0 {size_t reserved;size_t Block_size;
} __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};static void _I_TestClass_testMethods(TestClass * self, SEL _cmd) {void (*blockA)(int a) = ((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0, &__TestClass__testMethods_block_desc_0_DATA));if (blockA) {((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA, 1990);}
}
- 通过上述代码,可以发现 block 其实是一个结构体类型,底层实现会根据 __类名__方法名_block_impl_下标 (0 代表这个方法或者这个类中第 0 个 block):
struct __类名__方法名_block_impl_下标
三、关于变量的作用域
- c 语言的函数中可能使用的参数变量种类:
- 参数类型
- 自动变量(局部变量)
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
- 由于存储区域特殊,这其中有三种变量是可以在任何时候以任何状态调用的:
- 静态变量
- 静态全局变量
- 全局变量
- 而其它两种,则是有各自相应的作用域,超过作用域后,会被销毁。
四、block 的内部实现,结构体是什么样子?
- block 的内部实现如下:
struct __TestClass__testMethods_block_impl_0 {struct __block_impl impl; // 成员变量struct __TestClass__testMethods_block_desc_0* Desc; // desc 结构体声明// 构造函数// fp 函数指针// desc 静态全局变量初始化的 __main_block_desc_ 结构体实例指针// flags block 的负载信息(引用计数和类型信息),按位存储.__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
// 将来被调用的block内部的代码:block值被转换为C的函数代码
// 这里,*__cself 是指向Block的值的指针,也就相当于是Block的值它自己(相当于C++里的this,OC里的self)
// __cself 是指向__TestClass__testMethods_block_impl_0结构体实现的指针
// Block结构体就是__TestClass__testMethods_block_impl_0结构体.Block的值就是通过__TestClass__testMethods_block_impl_0构造出来的
static void __TestClass__testMethods_block_func_0(struct __TestClass__testMethods_block_impl_0 *__cself, int a) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_wx_b8tcry0j24dbhr7zlzjq3v340000gn_T_TestClass_9f58f7_mi_0,a);
}static struct __TestClass__testMethods_block_desc_0 {size_t reserved;size_t Block_size;
} __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};static void _I_TestClass_testMethods(TestClass * self, SEL _cmd) {void (*blockA)(int a) = ((void (*)(int))&__TestClass__testMethods_block_impl_0((void *)__TestClass__testMethods_block_func_0, &__TestClass__testMethods_block_desc_0_DATA));if (blockA) {((void (*)(__block_impl *, int))((__block_impl *)blockA)->FuncPtr)((__block_impl *)blockA, 1990);}
}
- 可以看得出来 __TestClass__testMethods_block_impl_0 有 3 个部分组成:
- impl 函数指针指向 __TestClass__testMethods_block_impl_0:
struct __block_impl {void *isa;int Flags;int Reserved; // 今后版本升级所需的区域void *FuncPtr; // 函数指针};
- Desc 指向 __TestClass__testMethods_block_impl_0的Desc 指针,用于描述当前这个 block 的附加信息,包括结构体的大小等信息:
static struct __TestClass__testMethods_block_desc_0 {size_t reserved; // 今后升级版本所需区域size_t Block_size; // block的大小} __TestClass__testMethods_block_desc_0_DATA = { 0, sizeof(struct __TestClass__testMethods_block_impl_0)};
- __TestClass__testMethods_block_impl_0() 构造函数,也就是该 block 的具体实现:
__TestClass__testMethods_block_impl_0(void *fp, struct __TestClass__testMethods_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }
- 此结构体中,isa 指针保持这所属类的结构体的实例的指针,struct __TestClass__testMethods_block_impl_0 相当于 Objective-C 类对象的结构体,_NSConcreteStackBlock 相当于 block 的结构体实例,也就是说 block 其实就是 Objective-C 对于闭包的对象实现。
五、block 是类吗?有哪些类型?
- block 也可以理解为类,因为它有 isa 指针、block.isa 的类型,包括:
- _NSConcreteGlobalBlock 跟全局变量一样,设置在程序的数据区域(.data)中;
- _NSConcreteStackBlock 栈上(上文的都是栈上 block);
- _NSConcreteMallocBlock 堆上。
- block 的 isa 可以按位运算。
六、一个 int 变量被 __block 修饰与否的区别?block的变量如何截获?
① 被 __block 修饰与否的区别
- 现有如下示例:
__block int a = 10;
int b = 20; PrintTwoIntBlock block = ^() { a -= 10; printf("%d, %d\n",a,b);
}; block(); // 0 20 a += 20;
b += 30; printf("%d, %d\n",a,b); // 20 50
block(); // 10 20
- 通过 __block 修饰 int a,block 体中对这个变量的引用是指针拷贝,它会作为 block 结构体构造参数传入到结构体中且复制这个变量的指针引用,从而达到可以修改变量的作用。
- int b 没有被 __block 修饰,block 内部对 b 是值 copy,因此在 block 内部修改 b 而不会影响外部 b 的变化。
② block的变量如何截获?
- 通过如下代码,来观察要一下变量的捕获:
blk_t blk;
{ id array = [NSMutableArray new]; blk = [^(id object){ [array addObject:object]; NSLog(@"array count = %ld",[array count]); } copy];
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
- 运行结果:
block_demo[28963:1629127] array count = 1
block_demo[28963:1629127] array count = 2
block_demo[28963:1629127] array count = 3
- 把上面的代码翻译成 C++:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id array; // 截获的对象 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc;}
};
- 在 Objc 中,C 结构体里不能含有被 __strong 修饰的变量,因为编译器不知道应该何时初始化和废弃 C 结构体。但是 Objc 的运行时库能够准确把握 block 从栈复制到堆,以及堆上的 block 被废弃的时机,实现上是通过 __TestClass__testMethods_block_copy_0 函数和 __TestClass__testMethods_block_dispose_0 函数进行:
static void __TestClass__testMethods_block_copy_0(struct __TestClass__testMethods_block_impl_0*dst, struct __TestClass__testMethods_block_impl_0*src) { _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __TestClass__testMethods_block_dispose_0(struct __TestClass__testMethods_block_impl_0*src) { _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
- _Block_object_assign 相当于 retain 操作,将对象赋值在对象类型的结构体成员变量中;
- _Block_object_dispose 相当于 release 操作。
- 这两个函数调用的时机是在什么时候呢?
函数 | 被调用时机 |
---|---|
__TestClass__testMethods_block_copy_0 | 从栈复制到堆时 |
__TestClass__testMethods_block_dispose_0 | 堆上的Block被废弃时 |
③ 什么时候栈上 block 会被复制到堆呢?
- 调用 block 的 copy 函数时;
- block 作为函数返回值返回时;
- 将 block 赋值给附有 __strong 修饰符 id 类型的类或者 block 类型成员变量时;
- 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中传递 block 时。
④ block 什么时候被废弃?
- 堆上的 block 被释放后,谁都不再持有 block 时调用 dispose 函数;
七、block 在修改 NSMutableArray 需不需要添加 __block?
- 修改 NSMutableArray 的存储内容的话,是不需要添加 __block 修饰的。
- 修改 NSMutableArray 对象的本身,那必须添加 __block 修饰。
八、block 怎么进行内存管理?
- 在上文中的 block 的构造函数 __TestClass__testMethods_block_impl_0 中的 isa 指针指向的是 &_NSConcreteStackBlock,它表示当前的 block 位于栈区中。
block内存操作 | 存储域/存储位置 | copy操作的影响 |
---|---|---|
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteStackBlock | 栈 | 从栈拷贝到堆 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
- 全局 block:_NSConcreteGlobalBlock 的结构体实例设置在程序的数据存储区,所以可以在程序的任意位置通过指针来访问,它的产生条件:
- 记述全局变量的地方有 block 语法时;
- block 不截获的自动变量。
- 以上两个条件只要满足一个就可以产生全局 block。
- 栈 block:_NSConcreteStackBlock 在生成 block 以后,如果这个 block 不是全局 block,那它就是栈 block,生命周期在其所属的变量作用域内(也就是说如果销毁取决于所属的变量作用域)。如果 block 变量和 __block 变量复制到了堆上以后,则不再会受到变量作用域结束的影响,因为它变成了堆 block。
- 堆 block:_NSConcreteMallocBlock 将栈 block 复制到堆以后,block 结构体的 isa 成员变量变成_NSConcreteMallocBlock。
九、block 可以用 strong 修饰吗?
- 在 ARC 中可以,因为在 ARC 环境中的 block 只能在堆内存或全局内存中,因此不涉及到从栈拷贝到堆中的操作。
- 在 MRC 中不行,因为要有拷贝过程,如果执行 copy 用 strong 的话会 crash,strong 是 ARC 中引入的关键字,如果使用 retain 相当于忽视了 block 的 copy 过程。
十、解决循环引用时,为什么要用 __strong、__weak 修饰 block?
- 首先 block 捕获变量的时候,结构体构造传入了self,造成了默认的引用关系,因此一般在 block 外部对操作对象会加上 __weak;
- 在 block 内部使用 __strong 修饰符的对象类型的自动变量,那么当 block 从栈复制到堆的时候,该对象就会被 block 所持有,但是持有的是上面加了 __weak,所以形成了彼消此涨的链条,刚好能解决 block 延迟销毁的时候对外部对象生命周期造成的影响,如果不这样做很容易造成循环引用。
十一、block 发生 copy 时机?
- 在 ARC 中,编译器将创建在栈中 block 会自动拷贝到堆内存中,而 block 作为方法或函数的参数传递时,编译器不会做 copy 操作。
- 调用 block 的 copy 函数时;
- block 作为函数返回值返回时;
- 将 block 赋值给附有 __strong 修饰符 id 类型的类或者 block 类型成员变量时;
- 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中传递 block 时。
十二、block 访问对象类型的 auto 变量时,在 ARC 和 MRC 下有什么区别?
- 在 ARC 下,栈区创建的 block 会自动 copy 到堆区;而 MRC 下,就不会自动拷贝,需要手动调用 copy 函数。
- block 的 copy 操作,当 block 从栈区 copy 到堆区的过程中,也会对 block 内部访问的外部变量进行处理,它会调用 block_object_assign 函数对变量进行处理,根据外部变量是 strong 还会 weak 对 block 内部捕获的变量进行引用计数 +1 或 -1,从而达到强引用或弱引用的作用。
- 因此在 ARC 下,由于 block 被自动 copy 到了堆区,从而对外部的对象进行强引用,如果这个对象同样强引用这个 block,就会形成循环引用。
- 在 MRC 下,由于访问的外部变量是 auto 修饰,所以这个 block 属于栈区的,如果不对 block 手动进行 copy 操作,在运行完 block 的定义代码段后,block 就会被释放,而由于没有进行 copy 操作,所以这个变量也不会经过 block_object_assign 处理,也就不会对变量强引用。
- 简而言之,ARC 下会对这个对象强引用,而 MRC 下则不会。
iOS经典面试题之深入分析block相关高频面试题相关推荐
- 谈谈对数据库索引的了解—这些就很重要!!附相关高频面试题。
本质:提高数据库性能的一种特殊文件 基础知识 面试题 一.索引的底层实现原理和优化 二. 三.什么情况下设置了索引但无法使用? 索引的类型有哪些,他们的区别是什么? 基础知识 概念: 索引是一种特殊的 ...
- 【面试题】2021年PHP高频面试题汇总
1.get,post 的区别 1.GET在浏览器回退时是无害的,而POST会再次提交请求. 2.GET产生的URL地址可以被Bookmark,而POST不可以. 3.GET请求会被浏览器主动cache ...
- Java面试题库,Java大厂高频面试题解析
一面 正式批(别看了都是正式批,提前批就没让我面!)一面.面试时间 08-18,19:53 - 21:08,全程1个小时15分钟.涉及内容:项目.网络.数据库.算法题 1. 自我介绍 2. 项目中的有 ...
- 计算机网络高频面试题最新版
在秋招过程中看了大量面经,将常见的计算机网络面试题总结如下,并按照面试中提问的频率做了标注(星数越高,面试中提问频率越高),如有帮到你,可以收藏点赞支持哦. 微信搜索公众号路人zhang,回复面试手册 ...
- 2022html css高频面试题
点击查看Vue高频面试题 点击查看JavaScript高频面试题 HTML+CSS高频面试题 html HTML全局属性有哪些 html新增加的语义化标签 div+css较table布局的的优势 介绍 ...
- MySQL高频面试题(最新版)
MySQL高频面试题,题目后面的星数越高,在面试中越高频 微信搜索公众号路人zhang,回复面试手册,领取更多高频面试题PDF版及更多面试资料. 面试手册在线版: www.mianshi.online ...
- MySQL数据库优化高频面试题(最新版)
MySQL数据库基础知识及优化高频面试题 微信搜索公众号路人zhang,回复面试手册,领取更多高频面试题PDF版及更多面试资料. 推荐阅读: Java基础知识高频面试题最新版 计算机网络高频面试题最新 ...
- 2022js高频面试题
点击查看HTML+CSS高频面试题 点击查看Vue高频面试题 js高频面试题 初级js面试题 给dom对象绑定事件都有那些方法 解释一下什么是事件流 删除事件有哪些方法 事件委托与事件冒泡讲一下 常见 ...
- 数据库索引高频面试题(最新版)
MySQL的索引是面试中的高频题目,将常见的索引面试题目总结了一下,如果有帮到你可以点赞收藏呦. 微信搜索公众号路人zhang,回复面试手册,领取更多高频面试题PDF版及更多面试资料. 面试手册在线版 ...
最新文章
- Spring Cloud(二): 注册中心Eureka的使用
- 修改Spring boot内置的tomcat端口
- android中线程和进程
- 32和64位jvm_我应该使用32位还是64位JVM?
- 大数据环境下的存储系统构建:挑战、方法和趋势
- 警告—系统—srv—2013—无
- 运筹作业题:一个正三角形平面,在三个角的部分减去一部分,然后沿着剪开部分折叠起来,使折叠后的三棱台体积最大
- 【Twitter】时序图神经网络
- 逗号,句号。问号?叹号!顿号、冒号:人名分隔·
- rank(),允许并列名次、复制名次自动空缺,结果如12245558……
- 记录一次es写入操作
- 计算机培训ppt课件,计算机基础操作培训ppt课件.ppt
- 用python做归结演绎推理_python基础归结
- 【vulhub系列】cve-2018-1273S pring Expression Language 漏洞复现
- 男人二十岁后应该学会的习惯
- 微信开启指纹支付功能 单账户每日限额5000元
- 京东网站页面编写(HTML、CSS、JS),包括京东秒杀的倒计时、轮播图等功能
- 医疗人工智能发展趋势及机遇
- 树莓派(USB麦克风和麦克风阵列) 录音和播放
- linux关于日志文件介绍,Linux下重要日志文件介绍