MST

星途 面试题库

面试题:Objective-C 中 Block 的实现原理及优化技巧

深入阐述 Objective-C 中 Block 的底层实现原理,包括 Block 的数据结构、捕获变量的方式等。同时,说明在实际项目开发中,针对 Block 性能优化有哪些技巧,为什么这些技巧能提高性能?
35.1万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

Block 的底层实现原理

  1. 数据结构
    • 在 Objective - C 中,Block 本质上是一个对象,它的底层数据结构包含三个部分:isa 指针,用于表明它是一个对象;flags 标志位,包含一些与 Block 特性相关的标志;以及函数指针,指向 Block 实际执行的代码。
    • 例如,对于捕获了外部变量的 Block,其数据结构还会包含捕获变量的存储空间。以一个简单的捕获局部变量的 Block 为例:
    int main() {
        int num = 10;
        void(^block)(void) = ^{
            NSLog(@"%d", num);
        };
        block();
        return 0;
    }
    
    • 这里的 block 底层数据结构除了基本的 isaflags 和函数指针外,还会有存储 num 的空间。
  2. 捕获变量的方式
    • 自动变量(局部变量):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;
    }
    
    每次调用 blocknum 都会自增。
    • 全局变量:Block 访问全局变量不需要捕获,直接可以访问和修改,因为全局变量在整个程序空间都可见。

Block 性能优化技巧及原因

  1. 避免在循环中创建 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 创建移到循环外,只进行一次内存分配,提高了性能。
  2. 合理使用 __block 修饰符
    • 技巧:只有在确实需要在 Block 内部修改外部变量时才使用 __block。因为使用 __block 修饰的变量,其捕获和存储方式相对复杂,会增加性能开销。
    • 原因__block 修饰的变量捕获时需要额外的操作来实现引用传递,相比默认的值传递,在编译和运行时都需要更多的处理,所以会影响性能。如果不需要在 Block 内部修改外部变量,就不要使用 __block,以减少不必要的开销。
  3. 使用 dispatch_once 初始化 Block
    • 技巧:对于一些需要单例模式或者只初始化一次的 Block,可以使用 dispatch_once。例如:
    static dispatch_once_t onceToken;
    static void(^block)(void);
    dispatch_once(&onceToken, ^{
        block = ^{
            // 初始化操作
        };
    });
    
    • 原因dispatch_once 保证代码块只执行一次,避免了重复初始化 Block 带来的性能开销,特别是在多次调用获取 Block 的场景下,能显著提高性能。