欢迎访问我的博客原文
内存泄漏指的是程序中已动态分配的堆内存(程序员自己管理的空间)由于某些原因未能释放或无法释放,造成系统内存的浪费,导致程序运行速度变慢甚至系统崩溃。
在 iOS 开发中会遇到的内存泄漏场景可以分为几类:
循环引用
当对象 A 强引用对象 B,而对象 B 又强引用对象 A,或者多个对象互相强引用形成一个闭环,这就是循环引用。
Block
Block 会对其内部的对象强引用,因此使用的时候需要确保不会形成循环引用。
举个例子,看下面这段代码:
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", self.name);
});
};
self.block();
block
是 self
的属性,因此 self
强引用了 block
,而 block
内部又调用了 self
,因此 block
也强引用了 self
。要解决这个循环引用的问题,有三种思路。
方法一:使用 Weak-Strong Dance
先用 __weak
将 self
置为弱引用,打破“循环”关系,但是 weakSelf
在 block
中可能被提前释放,因此还需要在 block
内部,用 __strong
对 weakSelf
进行强引用,这样可以确保 strongSelf
在 block
结束后才会被释放。
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.name);
});
};
self.block();
方法二:断开持有关系
使用 __block
关键字设置一个指针 vc
指向 self
,重新形成一个 self → block → vc → self
的循环持有链。在调用结束后,将 vc
置为 nil
,就能断开循环持有链,从而令 self
正常释放。
__block UIViewController *vc = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
vc = nil;
});
};
self.block();
这里还要补充一个问题,为什么要用 __block
修饰 vc
?
首先,block
本身不允许修改外部变量的值。但被 __block
修饰的变量会被存在了一个栈的结构体当中,成为结构体指针。当这个对象被 block
持有,就将“外部变量”在栈中的内存地址放到堆中,进而可以在 block
内部修改外部变量的值。
还有一种方式可以断开持有关系。就是将 self
以传参的形式传入 block
内部,这样 self
就不会被 block
持用,也就不会形成循环持有链。
self.block = ^(UIViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
});
};
self.block(self);
NSTimer
我们知道 NSTimer
对象是采用 target-action
方式创建的,通常 target
就是类本身,而我们为了方便又常把 NSTimer
声明为属性,像这样:
// 第一种创建方式,timer 默认添加进 runloop
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timeFire) userInfo:nil repeats:YES];
// 第二种创建方式,需要手动将 timer 添加进 runloop
self.timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timeFire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
这就形成了 self → timer → self(target)
的循环持有链。只要 self
不释放,dealloc
就不会执行,timer
就无法在 dealloc
中销毁,self
始终被强引用,永远得不到释放,循环矛盾,最终造成内存泄漏。
那么如果只把 timer
作为局部变量,而不是属性呢?
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timeFire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self
同样释放不了。
因为在加入 runloop 的操作中,timer
被强引用,这就形成了一条 runloop