Block 和内存管理

144 阅读2分钟

一、Block的三种类型与内存区域

Block类型内存区域触发条件内存管理
__NSGlobalBlock__全局区Block未捕获外部变量无需管理,类似全局函数
__NSStackBlock__栈区非ARC环境下捕获外部变量,未执行copy操作函数返回后自动释放
__NSMallocBlock__堆区捕获外部变量且执行copy操作(ARC自动处理)需手动管理(MRC)或自动管理(ARC)

二、内存管理核心规则

1. 变量捕获规则

  • 基本数据类型

    • 不加__block修饰:Block内部值拷贝,无内存管理问题
    • __block修饰:Block内部指针引用,需处理生命周期
  • 对象类型

    • Block默认对对象强引用(ARC/MRC均可能引起循环引用)
    • __block修饰符在MRC下可避免强引用,但在ARC下无效

2. 内存管理操作

  • MRC环境
    // 必须手动copy到堆
    void (^block)(void) = [^{ NSLog(@"MRC Block"); } copy];
    [block release]; // 使用后需释放
    
  • ARC环境
    • 大多数情况自动将Block从栈复制到堆
    • 例外情况:将Block赋值给strong/copy修饰的属性时自动执行copy

三、循环引用与解决方案

1. 典型场景

// self -> block -> self 形成循环引用
self.block = ^{
    [self doSomething]; 
};

2. 解决方案

  • 弱引用 + 强引用安全模式

    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf doSomething];
    };
    
  • 手动打破循环(MRC):

    // 使用__block修饰符避免强引用
    __block typeof(self) blockSelf = self;
    self.block = ^{
        [blockSelf doSomething];
        blockSelf = nil; // 执行后手动置nil
    };
    

四、关键实践建议

  1. ARC环境下优先使用

    @property (copy, nonatomic) void (^block)(void); // 必须用copy修饰
    
  2. 避免直接访问实例变量

    // 错误写法(隐式self引用)
    ^{ _name = @"Tom"; }; 
    // 正确写法(显式weakSelf)
    ^{ weakSelf.name = @"Tom"; };
    
  3. 特殊变量处理

    // 静态变量无需内存管理
    static int count = 0;
    ^{ count++; };
    

五、调试技巧

  1. 检测Block类型

    NSLog(@"Block类型: %@", [block class]); // 输出__NSGlobalBlock__等
    
  2. 内存泄漏检测工具

    • Xcode Memory Graph Debugger
    • InstrumentsLeaks工具

附:MRC与ARC对比

操作MRCARC
Block赋值必须显式copy自动copystrong/copy属性)
__block修饰对象不增加引用计数仍强引用(需配合__weak
释放时机手动release自动释放(引用计数为0时)