常见内存泄漏场景
- 对象创建后未释放:
- 当使用
alloc
、new
或 copy
方法创建对象时,如果之后没有调用 release
或 autorelease
,就会导致内存泄漏。例如:
NSString *str = [[NSString alloc] initWithString:@"Hello"];
// 此处没有调用 release 或 autorelease,会造成内存泄漏
- 循环引用:
- 两个或多个对象相互持有对方的强引用,形成循环引用,导致对象无法被释放。比如:
@interface ClassA;
@interface ClassB;
@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *classB;
@end
@interface ClassB : NSObject
@property (nonatomic, strong) ClassA *classA;
@end
@implementation ClassA
@end
@implementation ClassB
@end
// 使用时
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
a.classB = b;
b.classA = a;
// 此时 a 和 b 形成循环引用,都无法释放
- 使用
NSTimer
导致的泄漏:
- 如果将
self
作为 NSTimer
的 target
,并且 NSTimer
是在类的实例方法中创建且没有在适当时候 invalidate,会导致 self
无法释放。例如:
@interface MyViewController : UIViewController {
NSTimer *timer;
}
@end
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateUI) userInfo:nil repeats:YES];
}
- (void)updateUI {
// 更新 UI 的代码
}
// 这里没有在 viewWillDisappear 等合适时机调用 [timer invalidate],会造成 self 泄漏
@end
- 视图控制器相关泄漏:
- 在视图控制器的视图层级中,如果对视图的引用没有正确管理,例如在视图控制器销毁时,仍然有其他对象持有对该视图控制器视图的强引用,会导致视图控制器及其相关对象无法释放。
使用Instruments工具中的Leaks模板检测内存泄漏问题
- 启动Leaks模板:
- 打开Xcode,选择
Product
-> Profile
,在弹出的 Instruments
模板选择框中,选择 Leaks
模板,然后点击 Profile
按钮。这将启动应用程序,并开始使用 Leaks
工具监控内存泄漏。
- 操作应用程序:
- 在应用程序运行过程中,执行可能导致内存泄漏的操作,例如反复创建和销毁可能泄漏的对象,执行循环引用相关的逻辑等。
Leaks
工具会实时监测内存使用情况。
- 查看Leaks报告:
Leaks
工具界面主要有两个部分:时间轴和详细信息区域。
- 时间轴:时间轴上会显示内存使用量的变化曲线,以及检测到的内存泄漏事件的位置。泄漏事件在时间轴上以红色箭头标识。
- 详细信息区域:当在时间轴上选择一个泄漏事件时,详细信息区域会显示泄漏对象的相关信息,包括对象的类名、分配地址、保留计数等。还会展示对象的内存分配调用栈,通过调用栈可以追溯到对象是在哪个函数或方法中分配的,从而定位到代码中可能导致泄漏的位置。
- 在详细信息区域的
Call Tree
部分,可以看到函数调用层次结构。可以展开每个节点查看更详细的调用信息,通过分析这些信息可以确定泄漏发生的具体代码位置。例如,如果发现某个 alloc
操作后没有对应的 release
,就可以在调用栈中找到对应的 alloc
调用点,进而修改代码修复泄漏问题。
- 分析循环引用:
- 对于循环引用导致的泄漏,
Leaks
工具可能不会直接明确指出循环引用关系,但通过分析泄漏对象的保留路径(在详细信息区域的 Retain Cycles
部分,如果有循环引用会有相关显示),可以发现对象之间相互持有强引用的情况。通常需要结合代码逻辑,查看对象之间的属性设置等,来确定循环引用的具体形成原因,并通过将其中一个强引用改为弱引用等方式来打破循环引用。