在 Objective-C 中,block
是一种匿名函数,可以捕获上下文中的变量。为了确保 block
在内存管理中的正确性,通常使用 copy
修饰符。以下是详细原因和解释:
1. block 的内存管理
-
block 的类型:
-
NSGlobalBlock
:存储在全局区,不捕获任何外部变量。 -
NSStackBlock
:存储在栈区,捕获外部变量。 -
NSMallocBlock
:存储在堆区,由栈区 block 复制而来。
-
-
默认行为:
- 当 block 捕获外部变量时,默认是
NSStackBlock
,存储在栈区。 - 栈区的 block 在作用域结束后会被释放,如果此时尝试访问该 block,会导致崩溃。
- 当 block 捕获外部变量时,默认是
2. 为什么用 copy
修饰
-
将 block 从栈复制到堆:
- 使用
copy
修饰符可以将NSStackBlock
复制为NSMallocBlock
,存储在堆区。 - 堆区的 block 生命周期由引用计数管理,可以安全地在作用域外使用。
- 使用
-
避免野指针:
- 如果不使用
copy
,栈区的 block 在作用域结束后会被释放,后续访问会导致野指针问题。
- 如果不使用
-
ARC 下的行为:
- 在 ARC(自动引用计数)环境下,
copy
是默认行为,即使不显式使用copy
,编译器也会自动将 block 复制到堆区。 - 但在 MRC(手动引用计数)环境下,必须显式使用
copy
。
- 在 ARC(自动引用计数)环境下,
3. 代码示例
-
MRC 环境下:
typedef void (^MyBlock)(void); @interface MyClass : NSObject @property (nonatomic, copy) MyBlock block; @end @implementation MyClass - (void)setupBlock { int value = 10; self.block = [^{ NSLog(@"Value: %d", value); } copy]; // 必须使用 copy } @end
-
ARC 环境下:
@interface MyClass : NSObject @property (nonatomic, copy) MyBlock block; @end @implementation MyClass - (void)setupBlock { int value = 10; self.block = ^{ NSLog(@"Value: %d", value); }; // ARC 下会自动 copy } @end
4. 注意事项
-
循环引用:
- 使用
copy
修饰 block 时,如果 block 捕获了self
,可能导致循环引用。 - 解决方法:使用
__weak
弱引用打破循环。__weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf doSomething]; };
- 使用
-
性能影响:
-
copy
操作会涉及内存分配和复制,可能对性能有轻微影响。 - 但对于大多数场景,这种影响可以忽略。
-
5. 总结
- 在 Objective-C 中,使用
copy
修饰 block 是为了将 block 从栈区复制到堆区,确保其生命周期和内存安全。 - 在 MRC 环境下必须显式使用
copy
,而在 ARC 环境下编译器会自动处理。 - 使用
copy
时需注意循环引用问题,可以通过弱引用解决。