27

iOS内存管理之开发-实战篇

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzUyMDAxMjQ3Ng%3D%3D&%3Bmid=2247491327&%3Bidx=1&%3Bsn=bfa7dfb3ef00cc3ba57332ccacdd0616
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

上一篇文章我介绍了iOS内存管理的底层技术原理。平时不需要我们程序员来过多的关注,尤其进入ARC时代,我们程序员对于内存要做的事变得很简单,但是在我们开发过程中依然可能会出现内存泄漏。

那我们实际开发过程中会遇到哪些内存方面的问题呢?

下面我从两个方面给大家剖析下:

第一个是我们经常用到的property的修饰符。

第二个是我们常提到的循环引用。

Ynq2eq3.png!web

Ynq2eq3.png!web

property修饰符

修饰符分为三大类:

1、读写性修饰符:readwrite、readonly

2、内存相关修饰符: assign、weak、strong、copy以及目前已经不再使用的retain、release、unsafe_unretained

3、线程安全相关修饰符: atomic、nonatomic

我们只看内存相关的,retain、release、unsafe_unretained在MRC中使用的,我们不需要关注。那看下剩下的assign、weak、strong、copy

assign: 既可以修饰对象,也可以修饰基本类型。而在实际开发中我们只会修饰基本数据类型,为什么呢?因为assign修饰对象会产生悬空指针的问题,修饰的对象释放后,指针不会自动置成nil,此时再向对象发消息程序会崩溃

weak: 只能修饰对象,经常用来修饰delegate,如果修饰基本数据类型会报错:Property with 'weak' attribute must be of object type. weak修饰的对象释放后(引用计数变为0),指针会被自动置为nil,之后再向该对象发消息也不会崩溃(向nil发送任何消息都不会崩溃)。weak适用于delegate和block等引用类型,不会导致悬空指针问题,也不会循环引用,非常安全。

strong用来修饰对象,强引用对象,会改变对象 的引用数。

copy 用来修饰NSString,NSArray,NSDictionary。

了解了基础后,我们来看下下面几个问题

为什么assign修饰对象会造成悬空指针而weak不会?

NSString、NSArray,NSDictionary为什么用copy修饰不用strong

NSMutableString、NSMutableArray、NSMutableleDictionary用copy还是strong?

那我们先看下第一个问题,这个就要从我们内存管理上来看,一个对象在释放的时候会遍历弱引用表,来释放所有的弱引用指针,在上边讲解内存管理的时候我们讲到了weak指针会被存到弱引用表,所以weak的指针会在对象释放的时候被释放。而对于用assgin来修饰的指针不会被记录,所以不会再对象释放的时候去释放这个指针就会造成悬空指针。

第二个问题因为NSString、NSArray,NSDictionary有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

copy此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。当属性类型为NSString时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

第三个问题NSMutableString、NSMutableArray、NSMutableleDictionary我们要用strong来修饰,因为如果用copy修饰的话,copy出来的对象将会是NSString、NSArray、NSDictionary。这样的话我们如果对其进行可变的操作如增删改,则会引起程序的崩溃。

Ynq2eq3.png!web

Ynq2eq3.png!web

循环引用的常见场景及解决方案

1、block使用不当引起循环引用

2、NSTimer使用不当引起循环引用

3、Delegate修饰符用的不对引起的循环引用

block使用不当引起循环引用

我们看下下面的代码:

U7F3qer.png!web

success block会持有其函数内所有的变量, 比如self.

可怕的点在于,由于arc下强持有的变量无法被释放,而request方法为对象方法,也就是其被self所持有, 所以引用的过程也就是: self -> request方法 -> success block -> self。

形成了一个强引用的持有链条,当退出页面后,dealloc方法不会被调用.这一组内存就被占用了,所以有些App如果做的很差,用户就会发现自己越用越卡,经常内存溢出甚至导致设备重启,罪魁祸首就是内存泄露过多,当App占用过高的内存,直接被系统kill,亦称闪退。

那我们要怎么解决呢? 看下边的代码:

Q32uEvn.png!web

在这段代码中,我们使用了弱引用,这样我们打破了循环引用的圈,解决了block引起的循环引用。

NSTimer引起的循环引用,看下边代码:

在该代码中,self强持有timer对象,创建timer时self被作为target强持有,造成了循环引入如下图:

aiyqEvr.jpg!web

那我们首先想到的方法和block一样,我们将用__weak来修饰self是不是就可以了呢?

事实上这样是无效的,无论我们用strong还是weak修改在NSTimer中都会生成一个强引用指针来指向self。

那这样的循环引用我们应该怎么办呢?依然是打断循环链

第一种方案:我们自定义一个TimerWeak对象,然后利用Category的方法会覆盖宿主方法来替换创建NSTimer的主要方法。看下边的代码:

Zf6ji2i.jpg!web

是不是完美的断掉了NSTimer的循环引用,又不会给我们平时的工作带来更多的工作量呢?

在iOS10.0之后,苹果为我们提供了下面的api,使用block的方式来代替selector,如果App只需要支持到10.0之后的用户,那我们NSTimer使用时只需要考虑block引起的循环引用问题。无需做上边的处理。

E3QR7ne.jpg!web

Delegate修饰符使用错误会引起循环引用。

我们声明delegate的时候都会weak来修饰,其实也是为了解决循环引用。这个大家应该都很熟悉了就不多做介绍了。

参考链接: 

https://www.jianshu.com/p/015132faf9ee

https://segmentfault.com/a/1190000019975967

https://www.jianshu.com/p/a4892d1931f3

参考书籍:Objective-C高级编程:iOS与OS X多线程和内存管理


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK