关于内存泄露

提到内存泄露,很多人就会想到循环引用,再想到没有走dealloc方法,甚至把这三者等同起来。其实这是不对的,下面我用一个例子来说明这个问题。但是我想先问一个问题,我在某个控制器的viewDidLoad 写了这句代码 [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];。大家肯定知道这个控制器释放不了了,那大家知道它内存泄露的原因吗?
其实循环引用只是导致内存泄露的一种情况,虽然也是主要的情况。还有一种是单例或者类单例引起的内存泄露。某个对象的引用计数始终大于等于1的话,那么系统就不会回收它,它就内存泄露了。比如以上情况,NSTimer对象会被加入到当前的RunLoop,而整个程序运行期间,主线程只有一个(类似于单例),而这个timer会强引用它的target,也就是self对象,所以就有 主线程强引用timer对象强引用self。导致self的引用计数始终大于等于1。
也许这个例子还不好理解,那么我就再举一个单例造成内存泄露的例子。上代码

#import <Foundation/Foundation.h>

typedef void(^MyBlock)(void);

@interface DXSingleObj : NSObject // DXSingleObj是一个单例

+ (instancetype)sharedInstance;
@property (nonatomic, copy) MyBlock myBlock;

@end
#import "DXSingleObj.h"
@interface DXSingleObj ()

@end

@implementation DXSingleObj

+ (instancetype)sharedInstance
{
   static dispatch_once_t onceToken;
   static id instance;
   dispatch_once(&onceToken, ^{
       instance = [self new];
   });
   return instance;
}

- (void)dealloc
{
   NSLog(@"%s",__FUNCTION__);
}
@end

我们在某个控制器的viewDidLoad加上如下代码

- (void)viewDidLoad {
   [super viewDidLoad];
   
   DXSingleObj *dxObj = [DXSingleObj sharedInstance];
   dxObj.myBlock = ^{
       NSLog(@"%@",self.view);
   };
}

这种情况下,这个控制器就不会释放了,退出这个控制器,它的dealloc方法也不会走。 引用关系就是 单例强引用block属性强引用self。导致self不能被释放。
解决方法也很简单,加一个weak,代码如下

- (void)viewDidLoad {
   [super viewDidLoad];
   __weak DXViewController *weakSelf = self;
   DXSingleObj *dxObj = [DXSingleObj sharedInstance];
   dxObj.myBlock = ^{
       NSLog(@"%@", weakSelf.view);
   };
}

看起来weak解决内存泄露好像很好用,那么回到上面这句代码,我们把self改为weakSelf,那么它还会内存泄露吗?[NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(timeChange) userInfo:nil repeats:YES];
其实这里传weakSelf还是没用。到这里是不有点迷糊了?不是说好的weak是弱引用,我传弱引用还不能打断引用链条吗?其实weak和weakSelf不是万能的,要看到底能不能改变这个属性的强弱引用。因为NSTimer内部有一个strong类型的target成员变量,_target = target,我们传weakSelf的话,就是_target = weakTarget。所以weak到底能否生效的话是看weak能否影响这个属性的强弱类型。
所以循环引用只是内存泄露的子集。判断这个对象是否内存泄露要看它的引用计数,只要引用计数一直大于等于1,就内存泄露了。

如果控制器的dealloc方法走了,那么是不是说明一定就没有内存泄露了? 其实不是的,控制器的dealloc方法走了,只是说明这个控制器释放了,但不能说明它里面没有内存泄露。请看例子

// 这是一个普通类的头文件
#import <Foundation/Foundation.h>

@interface DXAction : NSObject
- (void)doAction;
- (void)cancelAction;
@end

// 再看.m文件
#import "DXAction.h"

@implementation DXAction
- (void)doAction
{
    NSLog(@"doAction---doAction");
    [self performSelector:@selector(doAction) withObject:nil afterDelay:1];
}
- (void)cancelAction
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)dealloc
{
    NSLog(@"%s",__func__);
}
@end
// 再看控制器的代码
#import "DXViewController.h"
#import "DXAction.h"

@interface DXViewController ()
@property (nonatomic, strong) DXAction *action;
@end

@implementation DXViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor orangeColor];

    DXAction *action = [[DXAction alloc] init];
    [action doAction];
    self.action = action;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc
{
    NSLog(@"%s",__FUNCTION__);
}
@end

点击进入控制器,然后退出控制器回到上个控制器,看到打印如下:

doAction---doAction
doAction---doAction
-[DXViewController dealloc]
doAction---doAction

说明控制器销毁了,但是这个DXAction对象还是没有销毁,内存泄露了。先说一下DXAction对象为啥不能销毁,这也是performSelector: withObject: afterDelay:使用不当造成内存泄露的原因, 这个方法会被当前线程(这里是主线程)强引用。除非这个方法执行完,否则self对象会一直被主线程强引用,不能释放。
我们平时的习惯是看一下控制器的dealloc方法有没有走,走了就觉得这个控制器没有内存泄露了,其实这是不完全可靠的

下面说一下我们经常用的dispatch_after(现在dispatch_after支持取消啦GCD延时取消),如果这个延时代码写在控制器里面

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       // 写代码 
    });

如果这个block里面没有self,那么退出界面,这个block还是会照常执行的。原因是因为主线程强引用这个block,直到block执行完才会将它置空。如果block里面有self的话,那么只有等到执行完这个block,那么这个控制器才能释放。控制器的延时释放,可能会有一些问题。
刚好这个dispatch_after可以借机来说一下weak-strong dance,还是上代码


#import "DXViewController.h"

@interface DXViewController ()
@property (nonatomic, copy) NSString *testStr;
@end

@implementation DXViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor orangeColor];
    self.testStr = @"test";
    __weak DXViewController *weakSelf = self;
    // 这里用dispatch_after的block模拟平时的block循环引用
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSMutableArray *arr = [NSMutableArray array];
        [arr addObject:weakSelf.testStr];
}

我见过很多人说,我一直用weak,没有配合strong一起使用,也从没出现过问题啊。那么,你跑一下这个代码,工程就会崩溃。什么时候用weak大家应该都比较清楚了,那么strong啥时候必须要配合使用,啥时候不用strong其实也没问题呢?
我先贴一下weak配合strong使用的标准代码吧,这也是苹果文档推荐的

    self.testStr = @"test";
    __weak DXViewController *weakSelf = self; // 1. 先用weakSelf
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        __strong DXViewController *strongSelf = weakSelf; //2. 再用strongSelf
        NSMutableArray *arr = [NSMutableArray array];
        if (strongSelf) { // 3. strongSelf判空
            [arr addObject:strongSelf.testStr];
        }
        else
        {
            NSLog(@"strongSelf为空啦");
        }
    });

跑一下这个工程,进入控制器后立即退出控制器,就会出现如下打印

-[DXViewController dealloc]
strongSelf为空啦

__strong DXViewController *strongSelf = weakSelf; 这个strongSelf只是一个局部变量,超过作用域后会释放,所以不会造成内存泄露的问题,而strongSelf又保证了在block执行期间,weakSelf不会中途被释放(前提是进入block时weakSelf就不为空)。但是__strong DXViewController *strongSelf = weakSelf;这句代码起不到使weakSelf不为空的作用,所以还是要对strongSelf判空一下。否则跑上面的代码,去掉strongSelf判断空就会崩溃。这才是完整的weak-strong dance。

其实如果能保证weakSelf在block执行完以前肯定不会为空的话,那么__strong DXViewController *strongSelf = weakSelf; 这句代码可以不用写的。如果不是对内存管理非常清楚的话,还是建议按上面这样写,只要使用了weak就一定配合strong使用,并且对strongSelf判空。

结论,那么平时应该如何正确使用weak呢?首先,weak不能乱用(weak比较耗性能),确定会造成block循环引用的情况下,用weak解决,然后用了weak就配合strong一起用,并且做好strongSelf判空。 当前,如果对这个weak的使用一点都不懂的话,看到block就用weak吧🙂。(这种人我还真见过不少)

最后出2个题,大家也经常遇到的
1.这段代码写在viewDidLoad中,会有问题吗? 如果造成了控制器释放不了,请写出它的引用链条。

 self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        NSLog(@"%@--self",self);
    }];

2.DXBlockClass类定义如下,

#import <Foundation/Foundation.h>
typedef void(^Block)(void);
@interface DXBlockClass : NSObject

@property (nonatomic, copy) Block myBlock;

+ (void)doBlock:(Block)block;
- (void)doBlock:(Block)block;
@end

#import "DXBlockClass.h"

@implementation DXBlockClass
+ (void)doBlock:(Block)block
{
    if (block) {
        block();
    }
}
- (void)doBlock:(Block)block
{
    _myBlock = block;
    if (block) {
        block();
    }
}
@end

在某个控制器的viewDidLoad写在这个代码,需要用weak解循环引用吗?为什么不会造成内存泄露(控制器的dealloc方法会走)?

- (void)viewDidLoad {
    [super viewDidLoad];

    DXBlockClass *blockClass = [[DXBlockClass alloc] init];
    [DXBlockClass doBlock:^{
        NSLog(@"%@-类方法-",self);
    }];
    [blockClass doBlock:^{
        NSLog(@"%@-对象方法-",self);
    }];
}

假如以前你对啥时候该用weak解内存泄露,啥时候该配合strong一起使用还不是非常了解的话。读完了这篇文章,如果能对weak和strong的使用了然于心的话,那么就请点一个喜欢吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容