iOS中内存管理漫谈

内存管理!MRC说一堆blabla(当然啦,各种release的痛想必记忆犹新),ARC说一堆blabla...

多么老生常谈的问题,想必每个开发者在起初不止一次的被问起这个问题,但是一旦被问起,一般大家都会说起'黄金法则’,然后@property,blabla一大堆…

大家都知道iOS的内存管理是通过引用计数来管理的,当引用计数为0的时候,对象会自动释放掉,在ARC环境下系统会“自动管理”对象的引用计数,然而事实上并非如此,我们常常遇到试图控制不能正常释放问题,block使用的时候内存泄露.

好吧,多么痛才能领悟,今天咱们就聊聊这个蛋疼的问题,当然啦是在ARC环境下,分别从这么几个地方聊聊

  • @property — 几个内存管理相关的修饰符
  • self — 大家有没有想过,selfARC下到底什么类型的,strong?weak?
  • block — 这玩儿大家每天都在写,但是说起来他的内存管理方式,不知道有多少能说全面的

Property

Property想必大家都写过,当然不排除还有很多一直在使用_ivar的,当然啦他们之间的区别以及该用那个好咱们今天不聊,如有想了解的同学请移步这里.属性的内存管理语义有 以下几个:

assign:适用于基本数据类型,其set方法只会进行简单的赋值操作NSInteger,CGFloat...

/** 基本数据类型*/
@property (assign, nonatomic) NSInteger age;

strong:强引用类型,被定义了该类型的属性会被其所指对象持有,为这种属性设置新值的时候,set方法先保留新值,并释放掉旧值,然后再把新值赋值上去

/** 适用于对象类型,并且被持有*/
@property (strong, nonatomic) UIView *view;

weak:爱恨交织的weak!弱引用类型,被定义了该类型的属性不会被持有,在set的时候,既不保留新值也不会释放旧值,和assign类似,不同的是在该属性所指对象被销毁之后,属性值也会被置为nil

/** 弱引用类型,对象释放后置为nil*/
@property (weak, nonatomic) id<SSDelegate> delegate;

unsafe_unretained:这个跟assign就更像了,但不同的是它适用于对象类型.同样的不会被持有,但与weak不同是,所指对象被销毁之后不会把属性置为nil

/** 弱引用类型,对象释放后不会置为nil*/
@property (unsafe_unretained, nonatomic) UIView *view;

copy:它与所指对象是持有关系,与strong类似,然而set方法并不会保留新值,而是将其拷贝一份.所以,当你声明一个非可变集合类型(collection type)的时候应该使用这个修饰符,不单单是NSString,同样的NSArray,NSDictionary,NSSet也应当使用它来修饰,copy可以保护非可变集合类型(collection type)的封装性,因为在其传递过程中有可能会指向一个可变类型(mutable type),此时如果不是copy,那么设置完属性之后,其值可能会在对象不知情的情况下被更改了.所以使用copy修饰符,保证set的时候copy一份不可变(immutable)类型的值,确保前后的一致性.

/** 适用于集合类型(collection type)*/
@property (copy, nonatomic) NSString *name;

Self

看到这个self估计很多人应该开始吐槽了,这SB,self有啥内存管理可言?需要管理么?

哥负责任的告诉你,需要!需要!需要!!!

行,你说需要就需要吧,哪里需要?Show me the code!!!

也许你可能会看到过这样代码,然后一头雾水的就略过了...

#import "SSYNetwork.h"

- (void)startRequestData {
    SSYNetwork *strongSelf = self;
    [strongSelf.delegate finishedRequestData:strongSelf];
    if (strongSelf.successCompletionBlock) {
        strongSelf.successCompletionBlock(strongSelf);
    }
    [strongSelf clearCompletionBlock];
} 

我头一次看到的时候也是一头雾水,这是干嘛呢?写得看起来很高大.仔细分析后就能够发现,这段代码之所以增加一行strongSelf,是为了防止提前释放导致crash.

这是一个请求网络数据的start方法,然而却在里面调用了finish方法,其实还没有等到start方法执行结束并返回的时候,self已经被finishedRequestData:方法释放,造成crash,简单的来说大致是这样的:

- (void)clickAvatar {
    // self持有delegate
    [self.delegate clickAvatar]; // clickAvatar这个代理方法释放了self
    // 这个时候self成了野指针,然后就Boom💣
}

那么现在我们可以考虑一下了,在ARC里面self到底是个什么状态?为何会这样?

其实ARC下,self既不是strong,也不是weak,而是被我们忽略的unsafe_unretained

在我们调用方法的时候,ARC不会对selfretainrelease,self的生命周期由他的调用方法来决定

所以self还是需要做一定的内存管理,不然一不小心就会💣(Boom)

Block

block其实是我们几乎每天都在用的东西,然而又让人讨厌的东西,之所以讨厌,是因为没有做好内存管理,产生循环引用导致内存泄露甚至是crash,下面咱们就唠唠这个烦人的玩意儿

声明方式

// 局部变量
returnType (^blockName)(parameterTypes) = ^returnType(parameters){...}

// 属性,作为属性的时候其修饰符一定是copy
@property (nonatomic, copy) returnType (^blockName)(parameters);

// 作为参数
- (void)someMethodThatTakesABlock:(returnType (^)(parameters))blockName;

// 方法调用是作为参数
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

// 宏定义
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

循环引用

在使用block的时候产生了循环引用是最让人头疼的,那么为什么会产生循环引用呢?先看看产生循环引用的代码长什么样,再分析分析why?

// 两个对象间相互引用
@property (nonatomic, copy) void(^block)(void);
self.block = ^{
    [self doSomething];
};

// 多个对象间相互引用,多对象相互引用更难以发现
ClassA* objA = [[ClassA alloc] init];
self.block = ^{
    [self doSomething];
};
self.objA = objA;

上面的这段代码中selfblock之间相互持有,在block里面调用的doSomething方法,使得self引用计数+1,这个过程中self是不会被释放,这就导致了循环引用.OK,那么问题来了,如何解决呢?

其实,解决这个问题就在于如何让block不持有self,要解决这个问题,只需要使用__weak声明一个弱引用(weakSelf)就可以了,代码如下:

@property (nonatomic, copy) void(^block)(void);

// 弱引用
__weak typeof(self) weakSelf = self; 

self.block = ^{
    [weakSelf doSomething];
};

搞定,这样之后我们再调用dealloc方法,打上断点,就可以看出self已经被释放了

Wow!💐💐💐💐💐

不要天真的以为这样就万事大吉,就可以丢下鼠标等胜利了!

如果我说,有时候即使你使用了__weak做了弱引用,也还是有crash的危险!

咱不逗行么.png

请看下面的代码:

// 弱引用
__weak typeof(self) weakSelf = self; 

// 异步线程,这里的block是作为一个参数的
dispatch_async(dispatch_get_main_queue(), ^{
    weakSelf.xx = xx;
});

让我们分析一下原因,将block作为参数传递给dispatch_async时,系统会将block拷贝到堆(heap)上,如果block中使用了self持有的对象,就会retain self,因为dispatch_async并不知道self会在什么时候被释放,所以为了确保系统调度执行block中的任务时self没有被意外释放掉,dispatch_async必须自己retain一次self,任务完成后在release self.但是这里使用了__weak,使得dispatch_async没有增加self的引用计数,这就导致了在调度执行block之前,self可能已经被释放掉了,然后就crash💣,控制台提示BAD_ACCESS

好吧,算你对了,那你说该咋办?

嘿嘿,既然没有retain,那就想办法retain呗?代码如下:

开启装逼模式.jpg
// 弱引用
__weak typeof(self) weakSelf = self; 

// 异步线程,这里的block是作为一个参数的
dispatch_async(dispatch_get_main_queue(), ^{
    // 加强引用防止提前释放,并且做判断
    __strong typeof(weakSelf) strongSelf = self; 
    if (strongSelf) {
        strongSelf.xx = xx;
    }
});

然而这样写就好了么?还有没有更好的方法?程序员都是有追求的,我们是一群高逼格的从业者!

再装逼试试.jpg

好吧,我只是想说还有更优雅的方式来写下面两句话

__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
别激动,开个小玩笑.jpg

有使用过ReactiveCocoa的同学应该知道,其实我们可以使用影子变量@weakify/@strongify这种方式同样能解决,至于关于影子变量的详细介绍这里就不多说了,有兴趣的同学可以到这里看看,使用影子变量后,代码就会像下面这样:

@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
    @strongify(self);
    if (!self) return;
    self.xx = xx; // 这里就可以放心的使用self,简单粗暴
});

OK,装逼结束!!!
如有错误之处,欢迎讨论.

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

推荐阅读更多精彩内容