面试题答案
一键面试内存分析与定位内存泄漏点
- 使用 Instruments 工具:
- Leaks 工具:在 Xcode 中,选择 Product -> Profile 打开 Instruments,选中Leaks模板。运行应用,Leaks 工具会实时监测内存分配和释放情况,当检测到无法释放的内存块时,会标记为潜在的内存泄漏,并展示泄漏对象的相关信息,如类名、分配地址等,帮助定位可能发生泄漏的代码位置。
- Allocations 工具:同样在 Instruments 中,Allocations 工具可以追踪应用运行过程中所有的内存分配情况。通过观察内存分配的增长趋势,结合时间轴和对象生命周期分析,判断哪些对象的创建没有对应的释放,进而找到内存泄漏的线索。例如,如果某个对象持续创建但没有看到其释放记录,很可能就是泄漏点。
- 代码审查:
- 检查强引用循环:在 Objective - C 中,对象之间的强引用循环是常见的内存泄漏原因。仔细检查代码中对象之间的引用关系,确保不存在两个或多个对象相互强引用的情况。例如,在自定义视图控制器中,如果视图控制器 A 持有视图控制器 B 的强引用,而 B 又反过来持有 A 的强引用,就会形成强引用循环,导致这两个控制器及其相关对象无法释放。
- 检查 block 中的引用:在使用 block 时,注意 block 内部对外部对象的引用。如果 block 被持有,且 block 内部强引用了外部对象,可能会导致外部对象无法释放。例如:
__weak typeof(self) weakSelf = self;
self.completionBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
// 使用 strongSelf 操作
};
这里使用 __weak
修饰符先创建一个弱引用,然后在 block 内部再创建一个强引用,以确保在 block 执行期间对象不会被释放,同时又避免了强引用循环。
优化内存使用
- 及时释放不再使用的对象:确保在对象不再被需要时,尽快释放其占用的内存。例如,在视图控制器的
dealloc
方法中,取消网络请求、移除通知监听等操作,防止对象因这些额外的引用而无法释放。 - 使用合适的数据结构:根据实际需求选择合适的数据结构,避免使用过大或不必要复杂的数据结构导致内存浪费。比如,如果只需要存储少量不重复的数据,使用
NSSet
可能比NSArray
更合适,因为NSSet
查找效率更高且占用内存相对较少。 - 缓存复用:对于频繁创建和销毁的对象,可以考虑使用缓存机制进行复用。例如,在自定义视图的重用机制中,UITableView 的 cell 复用机制,可以显著减少内存分配和释放的开销。
ARC 环境下的内存管理陷阱及解决方案
- 强引用循环:
- 陷阱:如上述提到的对象之间相互强引用,在 ARC 环境下同样会导致内存泄漏。例如,两个视图控制器相互持有对方的强引用,即使它们都不再被视图层级所需要,由于相互引用,ARC 无法自动释放它们。
- 解决方案:使用弱引用(
__weak
)或无主引用(__unsafe_unretained
)打破强引用循环。__weak
引用的对象在被释放时,引用会自动置为nil
,避免了野指针问题;而__unsafe_unretained
引用的对象被释放后,引用不会置为nil
,可能导致野指针,使用时需谨慎。
- block 中的强引用:
- 陷阱:当 block 被持有(例如作为属性或参数传递),并且 block 内部强引用了外部对象时,可能会导致外部对象无法释放。例如:
@interface MyViewController ()
@property (nonatomic, copy) void (^completionBlock)();
@end
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.completionBlock = ^{
NSLog(@"%@", self.title);
};
}
@end
这里 block 内部强引用了 self
,如果 completionBlock
被持有,self
就无法释放,可能导致内存泄漏。
- 解决方案:如前文所示,使用 __weak
修饰符先创建一个弱引用指向 self
,然后在 block 内部再创建一个强引用,确保在 block 执行期间对象不会被释放,同时避免强引用循环。
3. NSTimer 导致的内存泄漏:
- 陷阱:当使用 NSTimer
并将 self
作为 target 时,如果 NSTimer
没有及时 invalidate,self
会因为被 NSTimer
强引用而无法释放,即使 self
已经不再被其他地方需要。例如:
@interface MyViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateUI) userInfo:nil repeats:YES];
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
@end
如果忘记在 dealloc
方法中 invalidate timer
,就可能导致内存泄漏。
- 解决方案:在对象的 dealloc
方法中,确保及时 invalidate NSTimer
,并将其设置为 nil
,打破强引用关系,使对象能够正常释放。