MST
星途 面试题库

面试题:Objective-C块(Block)的内存管理及捕获变量机制

详细阐述Objective-C中块(Block)是如何进行内存管理的,包括何时会导致内存泄漏以及块对外部变量的捕获机制是怎样的,结合代码示例说明。
36.9万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

Block的内存管理

  1. Block的存储类型
    • NSStackBlock:栈上的Block,当函数返回时,栈上的Block会被销毁。例如:
void testStackBlock() {
    int a = 10;
    void (^stackBlock)() = ^{
        NSLog(@"%d", a);
    };
    stackBlock();
}
  • NSMallocBlock:堆上的Block,需要手动管理内存。一般通过copy操作将栈上的Block复制到堆上。例如:
void testMallocBlock() {
    int a = 10;
    void (^stackBlock)() = ^{
        NSLog(@"%d", a);
    };
    void (^heapBlock)() = [stackBlock copy];
    // 使用完后,如果是ARC环境,ARC会自动释放,如果是MRC环境,需要手动释放
    // 在MRC下:[heapBlock release];
}
  • NSGlobalBlock:全局区的Block,当Block中不捕获任何外部变量时,会被存储在全局区,不需要进行内存管理。例如:
void (^globalBlock)() = ^{
    NSLog(@"Global Block");
};
  1. ARC下的内存管理
    • 在ARC环境下,编译器会自动对Block进行retainrelease操作。当Block被捕获到对象的属性或者作为函数返回值时,会自动retain。例如:
@interface MyClass : NSObject
@property (nonatomic, copy) void (^blockProperty)();
@end

@implementation MyClass
- (void)setUpBlock {
    int a = 10;
    self.blockProperty = ^{
        NSLog(@"%d", a);
    };
}
@end
  • 当对象被释放时,其属性中的Block会自动release
  1. MRC下的内存管理
    • 在MRC环境下,需要手动对Block进行retainrelease操作。如果从栈上复制Block到堆上,使用copy方法,这相当于retain操作。例如:
@interface MyClass : NSObject
@property (nonatomic, copy) void (^blockProperty)();
@end

@implementation MyClass
- (void)setUpBlock {
    int a = 10;
    void (^stackBlock)() = ^{
        NSLog(@"%d", a);
    };
    self.blockProperty = [stackBlock copy];
    // 最后需要在合适的地方手动释放
    // [self.blockProperty release];
}
@end

内存泄漏情况

  1. 循环引用导致内存泄漏
    • 当Block捕获对象并持有对象,而对象又持有该Block时,会产生循环引用导致内存泄漏。例如:
@interface MyClass : NSObject
@property (nonatomic, copy) void (^blockProperty)();
@end

@implementation MyClass
- (void)dealloc {
    NSLog(@"MyClass dealloc");
    [super dealloc];
}
- (void)setUpBlock {
    self.blockProperty = ^{
        NSLog(@"Inside block, self = %@", self);
    };
}
@end

int main() {
    MyClass *obj = [[MyClass alloc] init];
    [obj setUpBlock];
    [obj release];
    return 0;
}
  • 在上述代码中,blockProperty捕获了selfself又持有blockProperty,导致循环引用,MyClass对象无法被释放。
  • 解决方法是使用__weak(ARC)或__unsafe_unretained(MRC)修饰符来打破循环引用。例如在ARC下:
@interface MyClass : NSObject
@property (nonatomic, copy) void (^blockProperty)();
@end

@implementation MyClass
- (void)dealloc {
    NSLog(@"MyClass dealloc");
}
- (void)setUpBlock {
    __weak typeof(self) weakSelf = self;
    self.blockProperty = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"Inside block, self = %@", strongSelf);
        }
    };
}
@end

Block对外部变量的捕获机制

  1. 自动变量(局部变量)的捕获
    • Block会捕获其定义时外部作用域的自动变量的值。例如:
void testCapture() {
    int a = 10;
    void (^block)() = ^{
        NSLog(@"%d", a);
    };
    a = 20;
    block();
}
  • 上述代码输出10,因为Block捕获的是定义时a的值。
  1. __block修饰的自动变量的捕获
    • 使用__block修饰的自动变量,Block捕获的是变量的引用,而不是值。例如:
void testBlockVariable() {
    __block int a = 10;
    void (^block)() = ^{
        a = 20;
    };
    block();
    NSLog(@"%d", a);
}
  • 上述代码输出20,因为Block捕获的是a的引用,可以在Block内部修改其值。
  1. 静态变量和全局变量的捕获
    • Block对静态变量和全局变量的捕获与普通访问一样,捕获的是变量的引用。例如:
static int staticVar = 10;
void testStaticAndGlobal() {
    void (^block)() = ^{
        staticVar = 20;
    };
    block();
    NSLog(@"%d", staticVar);
}
  • 上述代码输出20,Block可以直接修改静态变量的值。对于全局变量同理。