MST

星途 面试题库

面试题:Objective-C中Block的底层实现与性能优化

深入探讨Objective-C中Block的底层实现机制,包括其数据结构、存储类型等。在实际项目中,从底层实现的角度出发,如何对使用Block的代码进行性能优化?请结合具体场景和代码进行分析。
41.9万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

Block的底层实现机制

数据结构

在Objective-C中,Block本质上是一个对象,其底层数据结构是一个struct。这个结构体包含了一些与Block相关的信息:

  1. isa指针:用于表明该Block对象所属的类。所有对象都有isa指针,通过它可以找到对象的类信息。
  2. 调用函数指针:指向实际执行Block代码的函数。当Block被调用时,会通过这个指针找到具体的执行代码。
  3. 捕获变量相关信息:如果Block捕获了外部变量,结构体中会包含这些变量的相关信息,比如捕获变量的值或者对变量的引用。

例如,以下是一个简化的Block底层结构体示意(实际结构更复杂):

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

存储类型

  1. NSStackBlock:栈上的Block,生命周期与栈相关。当定义Block的函数返回时,栈上的Block会被销毁。这种类型的Block主要在函数内部定义且未进行拷贝操作时存在。例如:
void testFunction() {
    int value = 10;
    void (^stackBlock)() = ^{
        NSLog(@"%d", value);
    };
    // 这里的stackBlock是NSStackBlock类型
}
  1. NSMallocBlock:堆上的Block,需要手动或自动地进行内存管理(通过copyrelease等操作)。通常在将栈上的Block拷贝到堆上时(例如将Block作为参数传递给需要持有它的函数,或者将Block赋值给一个强引用的属性等情况),会变成NSMallocBlock类型。例如:
@property (nonatomic, strong) void (^heapBlock)();

- (void)assignBlock {
    int value = 10;
    void (^stackBlock)() = ^{
        NSLog(@"%d", value);
    };
    self.heapBlock = stackBlock; // stackBlock被拷贝到堆上,变为NSMallocBlock
}
  1. NSGlobalBlock:全局的Block,存储在程序的全局数据区,生命周期伴随整个程序。当Block没有捕获任何外部变量时,会是这种类型。例如:
void (^globalBlock)() = ^{
    NSLog(@"Global Block");
};

性能优化

避免不必要的捕获

  1. 具体场景:在一个频繁调用的方法中,Block捕获了大量不必要的外部变量。
  2. 代码示例
@interface ViewController ()
@property (nonatomic, strong) NSArray *largeDataArray;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.largeDataArray = @[@"a", @"b", @"c", @"d", @"e", ..., @"z"]; // 假设有大量数据

    for (int i = 0; i < 1000; i++) {
        [self performTaskWithBlock:^{
            NSLog(@"%@", self.largeDataArray); // Block捕获了largeDataArray
        }];
    }
}

- (void)performTaskWithBlock:(void (^)())block {
    block();
}

@end
  1. 优化方法:只捕获真正需要的变量,避免捕获大对象。可以在Block内部重新获取需要的数据,而不是直接捕获。例如:
@interface ViewController ()
@property (nonatomic, strong) NSArray *largeDataArray;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.largeDataArray = @[@"a", @"b", @"c", @"d", @"e", ..., @"z"];

    for (int i = 0; i < 1000; i++) {
        NSArray *dataToUse = self.largeDataArray; // 先获取需要的数据
        [self performTaskWithBlock:^{
            NSLog(@"%@", dataToUse); // 捕获小的局部变量
        }];
    }
}

- (void)performTaskWithBlock:(void (^)())block {
    block();
}

@end

合理管理Block的生命周期

  1. 具体场景:在一个视图控制器中,有一个Block作为属性,且该Block捕获了视图控制器本身(self),可能会导致循环引用,从而引起内存泄漏。
  2. 代码示例
@interface ViewController ()
@property (nonatomic, strong) void (^block)(void);
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.block = ^{
        NSLog(@"%@", self); // Block捕获了self
    };
}

@end
  1. 优化方法:使用__weak或者__unsafe_unretained修饰符来打破循环引用。例如:
@interface ViewController ()
@property (nonatomic, strong) void (^block)(void);
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"%@", strongSelf);
        }
    };
}

@end

减少Block的创建次数

  1. 具体场景:在一个循环中频繁创建相同逻辑的Block。
  2. 代码示例
for (int i = 0; i < 1000; i++) {
    void (^block)() = ^{
        NSLog(@"Block execution");
    };
    block();
}
  1. 优化方法:将Block的创建移到循环外部,避免每次循环都创建新的Block对象。例如:
void (^block)() = ^{
    NSLog(@"Block execution");
};
for (int i = 0; i < 1000; i++) {
    block();
}