面试题答案
一键面试可能出现的性能问题
- 循环引用:
- 当Block捕获了外部对象(如视图控制器实例),而Block又被该对象持有(例如作为属性),就会形成循环引用,导致对象无法释放,造成内存泄漏。例如,在视图控制器中定义一个Block属性,并在Block内部访问视图控制器的属性:
@interface ViewController : UIViewController @property (nonatomic, copy) void (^block)(void); @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.block = ^{ NSLog(@"%@", self.title); }; } @end
- 这里
self.block
持有了self
,而self
又持有self.block
,形成循环引用。
- 大量数据持有导致内存峰值过高:
- 由于部分Block持有大量数据,在Block的生命周期内,这些数据一直占用内存。如果Block的生命周期过长(例如被一个全局变量持有),可能会导致应用程序的内存峰值过高,甚至引发内存警告或应用崩溃。
优化方案
- 解决循环引用:
- 使用
__weak
关键字:在Block内部使用__weak
修饰符来捕获外部对象,以打破循环引用。例如:
@interface ViewController : UIViewController @property (nonatomic, copy) void (^block)(void); @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf) { NSLog(@"%@", strongSelf.title); } }; } @end
- 这里先使用
__weak
修饰符创建一个弱引用weakSelf
,在Block内部再创建一个强引用strongSelf
。这样在Block执行期间,如果self
没有被释放,strongSelf
会保持self
的存在,执行完Block后,strongSelf
释放,self
有可能被释放,从而打破循环引用。
- 使用
- 优化Block声明:
- 尽量使用
__block
而不是__strong
:如果Block只是需要修改外部局部变量,使用__block
修饰符,而不是让Block默认以强引用捕获变量。例如:
- (void)testBlock { __block int num = 10; void (^block)(void) = ^{ num = 20; }; block(); NSLog(@"%d", num); }
- 这里使用
__block
修饰num
,避免了不必要的强引用。
- 尽量使用
- 优化Block赋值:
- 适时赋值:在需要使用Block时再进行赋值,避免过早赋值导致Block过早持有对象。例如,在视图控制器的
viewDidAppear:
方法中赋值Block,而不是在viewDidLoad
中,这样可以减少Block在视图控制器未显示时占用的内存。
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; __weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf) { // 执行相关操作 } }; }
- 适时赋值:在需要使用Block时再进行赋值,避免过早赋值导致Block过早持有对象。例如,在视图控制器的
- 优化Block释放时机:
- 及时释放:当确定不再需要Block时,将其设置为
nil
。例如,在视图控制器的dealloc
方法中:
- (void)dealloc { self.block = nil; }
- 这样可以确保Block及其持有的对象能够被及时释放。
- 及时释放:当确定不再需要Block时,将其设置为
- 对持有对象的处理策略:
- 减少不必要的持有:检查Block是否真的需要持有大量数据,如果可以在Block执行时再获取数据,尽量不要在Block创建时就持有。例如,对于一些网络请求结果数据,如果在Block执行时可以重新发起请求获取最新数据,就不要在Block创建时持有整个数据。
- 使用自动释放池:如果Block内部有大量临时对象创建,可以在Block内部使用自动释放池来控制内存峰值。例如:
self.block = ^{ @autoreleasepool { // 创建大量临时对象的代码 for (int i = 0; i < 10000; i++) { NSString *str = [NSString stringWithFormat:@"%d", i]; } } };
- 这样在自动释放池结束时,内部创建的临时对象会被释放,降低内存峰值。