美文网首页
iOS MRC、ARC 与 @autoreleasepool 学

iOS MRC、ARC 与 @autoreleasepool 学

作者: DesmondDAI | 来源:发表于2018-07-21 18:23 被阅读174次

从 2016 年开始 iOS 开发到现在,几乎所有的项目里都只使用 ARC,偶尔遇到历史遗留的少数文件用到 MRC,但因为不需要修改就直接忽略,也觉得 MRC 这么古董的内存管理方式即使学会了也没有用武之地。但是最近想法有了改变,觉得使用某个框架编程,不仅需要能够熟练使用 API 来实现工程需求,而且需要了解其底层的运作机制。因为当项目做深入了需要优化的时候,就需要了解底层的原理来对项目提出解决方案。而且了解底层对某些时候的 debug 效率有很大的提升。我不想只停留在浅层次的工具使用技能,也想更多了解工具的原貌、设计与运作逻辑。

iOS 的内存管理机制

iOS 通过对象的指针引用数来管理其在内存中的持有与释放。因此每一个运行起来的应用,其内存的对象都构成了一幅对象图(object graph),开发者的一个很重要的职责就是管理好对象图,以保证应用在有限的内存分配中高效运行,避免内存泄露(memory leak)等问题的发生。


object graph example.png

iOS 的内存管理机制与 Android 的垃圾回收机制(garbage collection)不同。

  • 垃圾回收机制:通过后台进程,从应用入口开始扫描完整的对象引用树来得知哪些对象已不被引用,从而销毁它们。
    • 好处:让程序员卸下繁琐且易出错的内存管理重担,而更专注在项目的业务逻辑的开发上。
    • 缺点:需要运行后台进程并消耗计算性能,某些时候对应用的性能影响较大。
  • 引用数管理机制:当对象的引用数为零时就会被系统释放,否则将被保留
    • 好处:机制简单,对应用性能影响较小
    • 缺点:容易出错,例如内存泄露,最常见是循环引用(reference cycle),需要额外的开发者劳动成本来避免

在这种引用数管理机制下,粗略学习了 MRC、ARC 与 @autoreleasepool 的概念与常用情况,以下作对应介绍。

MRC

全称 Manual Reference Counting, 是需要开发者手动标记对象以决定其在内存中的命运的运行机制。管理通过使用 retain, release, 以及 autorelease 的消息发送来实现。

retain: 持有(拥有)对象,对象引用数加 1
release: 释放对象,对象引用数减 1
autorelease: 通知系统,在 @autoreleasepool 代码块结束时,对对象调用 release

这里首先描述 Cocoa 框架的 4 条内存管理原则:

  1. 自己创建的对象,自己获得拥有权
    自己创建的对象指通过 alloc, new, copy, mutableCopy 创建的对象,例如:
Foo *foo = [[Foo alloc] init];

// foo 对象使用中...

[foo release];  // 释放内存
  1. 别人创建的对象,可以通过 retain 来获得拥有权
// 例如已有 fooArray, 通过 array 方法获得其引用
NSArray *bar = [fooArray array];

// 不可以直接调用 release,因为没有拥有权
[bar release];

// 需要先 retain 来获得拥有权,然后才能释放
[bar retain];
[bar release];
  1. 你所拥有的对象不再需要使用时,必须将其释放

  2. 不能释放你不拥有的对象
    见上,第 2 点例子

ARC

此处的 A 就是 automatic。其实 ARC 只是比 MRC 多了一步,就是在编译时编译器自动帮开发者添加 retain, release 以及 autorelease 的调用,底层的内存管理机制还是和 MRC 一样。

在 ARC 模式下,我们通常在对象变量的声明里用属性标记符来指引 ARC 机制来管理我们的对象变量,它们是:strong, retain, weak, copy, assign。默认标记是 strong

标记符的区别

  • strong: 顾名思义,就是强引用,对应 MRC 下的 retain,即引用数加 1
  • retain: 同 strong
  • weak: 弱引用,不增加引用数,引用的对象被释放后变为 nil
  • copy: 对对象进行 copy 后再赋值,因此对象必须遵循 NSCopying 协议。如:
@property(copy) Foo *foo;
...
self.foo = bar;  // 相当于 self.foo = [bar copy];
  • assign: 一般用于原始数据类型(primitive type)的赋值。可以用于对象,效果相当于 weak,可是有一个坑是当对象被释放后,assign 属性的变量不会变成 nil,而是成为野指针(dangling pointer),因此不建议使用在对象上。

借助以上的属性标记符,我们可以在对象声明的时候集中制定它们的内存管理策略,清晰明了。

@autoreleasepool

@autoreleasepool 其实就是显式化的 ARC,让开发者能够更精细地指引系统管理对象内存。但是既然有了 ARC,我们为什么还需要烦心去精细化操作呢?Apple 给出的其中一个 使用场景 就是降低内存使用峰值(reduce peak memory footprint)。假设这么一个使用场景:
在一个循环中,需要创建临时对象,而在 iOS 内存管理机制下,临时对象在循环结束时才会被释放。因此如果循环多次运行时,临时对象的内存占用就会快速累积,造成一个内存占用峰值。在这种情况下,我们就可以使用 @autoreleasepool 来及时释放对象。如以下官方例子:

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

如此在 autoreleasepool 的代码块中,每次用完一个 url,临时创建的 fileContents 以及 error(如有)就会在代码块结束时获得释放。

@autoreleasepool 怎么知道代码块中的哪些对象需要被释放?秘密就是在代码块中收到 autorelease 消息的对象(包括通过 alloc, new 等方法创建或者显式发送 autorelease)会在代码块结束时收到 release 消息。因此上述例子的 fileContents 在被创建时编译器隐式添加了 autorelease,在代码块结束时被对应发送 release

参考

相关文章

网友评论

      本文标题:iOS MRC、ARC 与 @autoreleasepool 学

      本文链接:https://www.haomeiwen.com/subject/stdhmftx.html