面试题答案
一键面试Block 的底层实现原理
- 数据结构:
- 在 Objective - C 中,Block 本质上是一个对象,它的底层数据结构包含三个部分:isa 指针,用于表明它是一个对象;flags 标志位,包含一些与 Block 特性相关的标志;以及函数指针,指向 Block 实际执行的代码。
- 例如,对于捕获了外部变量的 Block,其数据结构还会包含捕获变量的存储空间。以一个简单的捕获局部变量的 Block 为例:
int main() { int num = 10; void(^block)(void) = ^{ NSLog(@"%d", num); }; block(); return 0; }
- 这里的
block
底层数据结构除了基本的isa
、flags
和函数指针外,还会有存储num
的空间。
- 捕获变量的方式:
- 自动变量(局部变量):Block 捕获自动变量时,默认是值传递。即 Block 内部对捕获变量的修改不会影响外部变量的值。例如上述代码中,即使在 Block 内部尝试修改
num
,外部的num
值也不会改变。 - 静态变量(static 修饰的局部变量):Block 捕获静态变量时,是引用传递。Block 内部对静态变量的修改会影响到外部。例如:
每次调用int main() { static int num = 10; void(^block)(void) = ^{ num++; NSLog(@"%d", num); }; block(); block(); return 0; }
block
,num
都会自增。- 全局变量:Block 访问全局变量不需要捕获,直接可以访问和修改,因为全局变量在整个程序空间都可见。
- 自动变量(局部变量):Block 捕获自动变量时,默认是值传递。即 Block 内部对捕获变量的修改不会影响外部变量的值。例如上述代码中,即使在 Block 内部尝试修改
Block 性能优化技巧及原因
- 避免在循环中创建 Block:
- 技巧:将 Block 的创建移到循环外部。例如:
// 不好的做法 for (int i = 0; i < 1000; i++) { void(^block)(void) = ^{ // 一些操作 }; block(); } // 好的做法 void(^block)(void) = ^{ // 一些操作 }; for (int i = 0; i < 1000; i++) { block(); }
- 原因:在循环中创建 Block 会导致频繁的内存分配和释放,增加内存管理的开销。将 Block 创建移到循环外,只进行一次内存分配,提高了性能。
- 合理使用 __block 修饰符:
- 技巧:只有在确实需要在 Block 内部修改外部变量时才使用
__block
。因为使用__block
修饰的变量,其捕获和存储方式相对复杂,会增加性能开销。 - 原因:
__block
修饰的变量捕获时需要额外的操作来实现引用传递,相比默认的值传递,在编译和运行时都需要更多的处理,所以会影响性能。如果不需要在 Block 内部修改外部变量,就不要使用__block
,以减少不必要的开销。
- 技巧:只有在确实需要在 Block 内部修改外部变量时才使用
- 使用
dispatch_once
初始化 Block:- 技巧:对于一些需要单例模式或者只初始化一次的 Block,可以使用
dispatch_once
。例如:
static dispatch_once_t onceToken; static void(^block)(void); dispatch_once(&onceToken, ^{ block = ^{ // 初始化操作 }; });
- 原因:
dispatch_once
保证代码块只执行一次,避免了重复初始化 Block 带来的性能开销,特别是在多次调用获取 Block 的场景下,能显著提高性能。
- 技巧:对于一些需要单例模式或者只初始化一次的 Block,可以使用