面试题答案
一键面试Block的内存管理特点
- 自动变量捕获:Block会捕获其定义时所在作用域中的自动变量(局部变量),并将这些变量的值拷贝到Block内部。如果捕获的是对象类型,默认情况下是强引用。
- 存储类型:Block有三种存储类型,分别是NSStackBlock(栈上)、NSMallocBlock(堆上)和NSGlobalBlock(全局区)。
- NSStackBlock:当Block在栈上时,其生命周期与定义它的函数相同。函数返回后,Block将被销毁。这种类型的Block通常在函数内部定义且未被拷贝到堆上时存在。
- NSMallocBlock:当Block被拷贝到堆上时(例如通过
copy
方法),它的生命周期由引用计数管理。可以像管理其他堆上对象一样,通过retain
、release
和autorelease
来控制其引用计数。 - NSGlobalBlock:如果Block不捕获任何自动变量,它会被存储在全局区,生命周期与程序相同,不需要手动管理内存。
Block引起循环引用的原理
当一个对象(假设为objA
)持有一个Block,而这个Block又捕获了objA
本身(形成对objA
的强引用),就会形成循环引用。因为objA
持有Block,所以Block不会被释放;而Block又强引用objA
,导致objA
也不会被释放,从而造成内存泄漏。
解决循环引用的方法
- 使用
__weak
关键字(ARC环境下)- 原理:
__weak
修饰的变量是弱引用,不会增加对象的引用计数。当对象被释放时,指向它的__weak
变量会自动被设置为nil
,从而打破循环引用。 - 代码示例:
- 原理:
@interface ViewController ()
@property (nonatomic, strong) void (^block)(void);
@property (nonatomic, strong) NSObject *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf.obj);
};
self.obj = [[NSObject alloc] init];
}
@end
- 使用
__block
关键字(MRC环境下,ARC下使用__block
可能仍会导致循环引用,不推荐)- 原理:在MRC环境下,
__block
修饰的变量不会被Block强引用。当对象被释放时,Block内对该变量的引用会失效,从而打破循环引用。 - 代码示例:
- 原理:在MRC环境下,
@interface ViewController ()
@property (nonatomic, strong) void (^block)(void);
@property (nonatomic, strong) NSObject *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block ViewController *blockSelf = self;
self.block = ^{
NSLog(@"%@", blockSelf.obj);
blockSelf = nil; // 手动打破引用
};
self.obj = [[NSObject alloc] init];
}
@end
- 在Block执行完毕后手动打破引用
- 原理:在Block执行完成后,将对象对Block的引用设置为
nil
,从而打破循环引用。 - 代码示例:
- 原理:在Block执行完成后,将对象对Block的引用设置为
@interface ViewController ()
@property (nonatomic, strong) void (^block)(void);
@property (nonatomic, strong) NSObject *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf.obj);
weakSelf.block = nil;
};
self.obj = [[NSObject alloc] init];
self.block();
}
@end