苹果公司在2011年的全球开发者大会上指出,90%的应用崩溃与内存管理有关,其中最主要的原因是错误的内存访问和保留环所引起的内存泄漏。
程序级内存管理
iOS系统中使用引用计数来管理内存对象,当一个对象的计数器不为0的时候,表示这个对象需要被使用,故而是存活的,当计数器为0的时候则表示这个对象不需要被使用。
MRC
- 自己创建自己持有
- 非自己生成的对象,自己也可以持有
- 不需要的自己持有的对象时释放
- 非自己持有的对象无法释放
ARC
自动引用计数(ARC)是一种编译器功能,可提供OC对象的自动内存管理。
ARC
环境的作用范围是OC对象,而CoreFoundation
或者其它C类型的指针对象/结构体需要转换成OC对象才能够使用ARC
,通过以下三种桥接方式来利用ARC
:
__bridge
可以桥接OC
对象和Core Foundation
,而不改变被转换对象的持有对象,当ARC控制的OC对象释放时,对应的Core Foundation
对象指针也会被释放。__bridge_retained
和CFBridgingRetain
也能够桥接两者,不同的是它会变更被转换对象的持有状态,即会进行一步retain
让它的计数器+1,编译器不会自动管理Core Foundation
对象的内存,需要调用CFRelease(
)手动释放。__bridge_transfer
和CFBridgeRelease
也能够桥接两者,不同的是它会变更被转换对象的持有状态,即会进行一步release
。被转换的对象在赋值给目标对象后随之释放
在ARC环境下的OC对象的状态,可以用四种修饰符来说明:
__strong
这是默认的动作,表明着这个对象是存活状态,持有强引用的变量在超出其作用域时被废弃,随着强引用失效,引用的对象会随之释放。__weak
弱引用一个对象,即不改变被引用对象的持有状态(计数器不变),同时当这个对象被释放后这个使用__weak
修饰的变量会自动置为nil
。__unsafe_unretained
与weak类似不会修改被引用对象的持有状态(计数器不变),但这个对象释放后这个使用它修饰的变量的指针会悬空不会自动置为nil
。__autoreleasing
相当于MRC下[object autorelease]
或者ARC
下使用@autorelease{}
管理的变量,将这个变量标记为自动释放。
优化技巧
Block循环引用
先将强引用的对象转为弱引用指针,防止了Block
和对象之间的循环引用,再在Block
中将weakSelf
的弱引用转换成strongSelf
的强引用指针,防止多线程和ARC
环境下弱引用随时被释放的问题。
1 | __weak MyViewController *wself = self; |
降低内存峰值
Lazy Allocation,懒加载
1 | lazy var goodsImageView: UIImageView = { |
图片的读取
1 | imageView?.image = UIImage(named: name) |
- 带缓存,如果频繁读取小文件,用它只需要读取一次就好,但是缺点就是如果使用大图片会常驻内存,对于降低内存峰值不利
- 不带缓存,适合使用大图片,使用完就释放
NSData & 内存映射文件
1 | public init?(contentsOfFile path: String) |
第二种比第一个多一个Options
,第二种方式是创建了一个内存映射文件(NSDataReadingMapped
),提高读取的速度,也方便了clean dirty
回收。
NSAutoReleasePool
为什么在ARC
时代还需要使用自动释放池?原因就是为了避免内存峰值,常见的是一个很大的For
循环,里面不段生成autorelease
对象,(datawithContentofFile
返回的是autorelease
对象),其实每迭代一次,资源都已经用完了,不需要再使用,这个时候就可以释放,但是程序需要等到Runloop
结束的时候才可以释放,这就增大了内存的峰值。
1 | func loadBigData() { |
内存警告处理
收到内存警告时,所要做的:
- 尽可能释放资源,尤其是图片等占用内存多的资源,等需要的时候再进行重建
- 单例模式的滥用,会导致单例对象一直持有资源,在内存紧张的时候要进行释放。
- 进行缓存更推荐使用NSCache而不是NSDictionary,就是因为NSCache不仅线程安全,而且对存在compressed memory情况下的内存警告也做了优化,可以由系统自动释放内存。