学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

iOS内存管理法则与优化

苹果公司在2011年的全球开发者大会上指出,90%的应用崩溃与内存管理有关,其中最主要的原因是错误的内存访问和保留环所引起的内存泄漏。

程序级内存管理

iOS系统中使用引用计数来管理内存对象,当一个对象的计数器不为0的时候,表示这个对象需要被使用,故而是存活的,当计数器为0的时候则表示这个对象不需要被使用。

MRC

  1. 自己创建自己持有
  2. 非自己生成的对象,自己也可以持有
  3. 不需要的自己持有的对象时释放
  4. 非自己持有的对象无法释放

ARC

自动引用计数(ARC)是一种编译器功能,可提供OC对象的自动内存管理。

ARC环境的作用范围是OC对象,而CoreFoundation或者其它C类型的指针对象/结构体需要转换成OC对象才能够使用ARC,通过以下三种桥接方式来利用ARC

  1. __bridge 可以桥接OC对象和Core Foundation,而不改变被转换对象的持有对象,当ARC控制的OC对象释放时,对应的Core Foundation对象指针也会被释放。
  2. __bridge_retainedCFBridgingRetain也能够桥接两者,不同的是它会变更被转换对象的持有状态,即会进行一步retain让它的计数器+1,编译器不会自动管理Core Foundation对象的内存,需要调用CFRelease()手动释放。
  3. __bridge_transferCFBridgeRelease也能够桥接两者,不同的是它会变更被转换对象的持有状态,即会进行一步release。被转换的对象在赋值给目标对象后随之释放

在ARC环境下的OC对象的状态,可以用四种修饰符来说明:

  1. __strong这是默认的动作,表明着这个对象是存活状态,持有强引用的变量在超出其作用域时被废弃,随着强引用失效,引用的对象会随之释放。
  2. __weak弱引用一个对象,即不改变被引用对象的持有状态(计数器不变),同时当这个对象被释放后这个使用__weak修饰的变量会自动置为nil
  3. __unsafe_unretained与weak类似不会修改被引用对象的持有状态(计数器不变),但这个对象释放后这个使用它修饰的变量的指针会悬空不会自动置为nil
  4. __autoreleasing相当于MRC下[object autorelease]或者ARC下使用@autorelease{}管理的变量,将这个变量标记为自动释放。

优化技巧

Block循环引用

先将强引用的对象转为弱引用指针,防止了Block和对象之间的循环引用,再在Block中将weakSelf的弱引用转换成strongSelf的强引用指针,防止多线程和ARC环境下弱引用随时被释放的问题。

1
2
3
4
5
__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
__strong __typeof(wself) sself = wself; // 强引用一次
[sself.property removeObserver: sself forKeyPath:@"pathName"];
};

降低内存峰值

Lazy Allocation,懒加载

1
2
3
4
lazy var goodsImageView: UIImageView = {
let goodsImageView = UIImageView()
return goodsImageView
}()

图片的读取

1
2
imageView?.image = UIImage(named: name)
imageView?.image = UIImage(contentsOfFile: path)
  1. 带缓存,如果频繁读取小文件,用它只需要读取一次就好,但是缺点就是如果使用大图片会常驻内存,对于降低内存峰值不利
  2. 不带缓存,适合使用大图片,使用完就释放

NSData & 内存映射文件

1
2
public init?(contentsOfFile path: String)
public init(contentsOfFile path: String, options readOptionsMask: NSDataReadingOptions) throws

第二种比第一个多一个Options,第二种方式是创建了一个内存映射文件(NSDataReadingMapped),提高读取的速度,也方便了clean dirty回收。

NSAutoReleasePool

为什么在ARC时代还需要使用自动释放池?原因就是为了避免内存峰值,常见的是一个很大的For循环,里面不段生成autorelease对象,(datawithContentofFile返回的是autorelease对象),其实每迭代一次,资源都已经用完了,不需要再使用,这个时候就可以释放,但是程序需要等到Runloop结束的时候才可以释放,这就增大了内存的峰值。

1
2
3
4
5
6
7
8
func loadBigData() {
for i in 1...10000 {
autoreleasepool {
let data = NSData.dataWithContentsOfFile(
path, options: nil, error: nil)
}
}
}

内存警告处理

收到内存警告时,所要做的:

  1. 尽可能释放资源,尤其是图片等占用内存多的资源,等需要的时候再进行重建
  2. 单例模式的滥用,会导致单例对象一直持有资源,在内存紧张的时候要进行释放。
  3. 进行缓存更推荐使用NSCache而不是NSDictionary,就是因为NSCache不仅线程安全,而且对存在compressed memory情况下的内存警告也做了优化,可以由系统自动释放内存。

参考

  1. iOS 内存管理与优化
  2. 理解iOS中的内存结构
  3. NSString 的内存问题