iOS 内存泄漏场景与解决方案

欢迎访问我的博客原文

内存泄漏指的是程序中已动态分配的堆内存(程序员自己管理的空间)由于某些原因未能释放或无法释放,造成系统内存的浪费,导致程序运行速度变慢甚至系统崩溃。

在 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();

blockself 的属性,因此 self 强引用了 block,而 block 内部又调用了 self,因此 block 也强引用了 self。要解决这个循环引用的问题,有三种思路。

方法一:使用 Weak-Strong Dance

先用 __weakself 置为弱引用,打破“循环”关系,但是 weakSelfblock 中可能被提前释放,因此还需要在 block 内部,用 __strongweakSelf 进行强引用,这样可以确保 strongSelfblock 结束后才会被释放。

__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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值