编程学习网 > 编程语言 > iOS开发 > iOS 内存管理
2016
02-18

iOS 内存管理

1. 引用计数

iOS 的引用计数就类似于下图中进出办公室的开关灯流程。当一个人A进入前,办公室的引用数为0,进入后需要照明,因此开灯,引用数为1,B进入后,引用数为2,C 进入后引用数为3,以此类推。A 离开时,引用数-1,当最后一个人离开办公室时,引用数为0,不再需要照明,因此关灯。


开关灯

对应到 OC 对象的动作时,开灯=生成对象(alloc\new\copy\mutableCopy),需要照明=持有对象(retain),不需要照明=释放对象(release),关灯=废弃对象(dealloc)。


引用计数的内存管理

1.1 内存管理的原则

  • 自己生成的对象,自己持有
  • 也可持有非自己生成的对象
  • 释放不再需要自己持有的对象
  • 非自己持有的对象无法释放

注意这些原则里的一些关键词与方法的对应关系:
『生成』- alloc\new\copy\mutableCopy
『持有』- retain
『释放』- release
『废弃』- dealloc

下面分别来解释一下,这四条原则的含义:

1.1.1 自己生成的对象,自己持有

id obj = [[NSObject alloc] init]; id obj = [NSObject new]; id obj = [NSObject copy]; id obj = [NSObject mutableCopy]; // 注意 alloc\new\copy\mutableCopy 开头的驼峰式方法名,也生成并持有对象 id obj = [MyObject allocMyObj]; id obj = [MyObject newThatObj]; id obj = [MyObject copyThis]; id obj = [MyObject mutableCopyThat];

注意
alloc\new\copy\mutableCopy 开头非驼峰式命名的方法不适用上述规则。例如:allocate\newer\copying\mutableCopyed。

1.1.2 非自己生成的对象,也可持有

id obj = [NSMutableArray array];

obj是非『alloc\new\copy\mutableCopy』或以其开头的驼峰式命名方法创建,因此属于非自己生成的对象。如何持有对象呢?用 retain 啊~

id obj = [NSMutableArray array];
[obj retain];

1.1.3 释放不再需要自己持有的对象

用alloc\new\copy\mutableCopy』或以其开头的驼峰式命名方法生成并持有的对象,在不再需要的时候,要用 release 方法释放。

id obj = [[NSMutableArray alloc] init]; // do Something... [obj release];
id obj = [NSMutableArray array];
[obj retain]; // do Something... [obj release];

1.1.4 非自己持有的对象无法释放

释放非自己持有的对象时,会发生崩溃,例如

1)同一个对象被多次释放:

id obj = [[NSMutableArray alloc] init];
[obj release];
[obj release];

2)释放非自己持有的对象:

id obj = [obj0 object]; [obj release]; // obj 既不是 alloc\new\copy\mutableCopy 出来的,也没有 retain,因此没有被持有,不可以被释放

2. autorelease

有一个跟 release 类似的关键词autorelease,看这样一段代码:

- (id)object { id obj = [[NSMutableArray alloc] init];
  [obj autorelease]; return obj;
}

obj 对象在什么时候被释放呢?与 release 的区别是什么?


release和 autorelease 的区别


对象被 release 时,引用计数-1,当引用计数为0时,该对象被立即释放。而对象被 autorelease 时,引用计数不变,该对象被注册到自动释放池中,在一个运行周期结束时,自动释放池被倾倒(池中注册的对象被 release)。

autorelease 类似 C 语言中的局部变量的特性,局部变量超过其作用域时会被自动废弃,autorelease 对待对象实例与之类似。当超出 autorelease 的作用域时,对象实例的 release 方法被调用。与 C 语言局部变量不同的是,autorelease 可以设置其作用域。

for(int i = 0;i  < 10000; i ++){
  @autoreleasepool { // 在一个 runloop 周期内产生大量对象的代码 }
}

除了上述场景,总结一下需要显式调用 autoreleasepool 的情况:

显式使用@autoreleasepool:

  • autorelease 机制基于 UI framework,因此写 非UI framework的程序时,需要自己管理对象生存周期。

  • autorelease 触发时机发生在下一次runloop的时候。因此如何在一个大的循环里不断创建autorelease对象,那么这些对象在下一次runloop回来之前将没有机会被释放,可能会耗尽内存。这种情况下,可以在循环内部显式使用@autoreleasepool {}将autorelease 对象释放。

for (item in BigSet){ @autoreleasepool { //create large mem objects  }
}
  • 自己创建的线程。Cocoa的应用都会维护自己autoreleasepool。因此,代码里spawn的线程,需要显式添加autoreleasepool。

  • 很长的函数、很多中间变量时。
    正常情况下,你创建的变量会在超出其作用域的时候被释放掉。
    而如果函数写的很长,在函数运行过程中出现很多中间变量,占据了大量的内存,怎么办?
    用@autoreleasepool。
    在@autoreleasepool中创建的变量,会在@autoreleasepool结束的时候执行一次release,进行释放。其实@autoreleasepool就相当于一层作用域。


3. ARC的规则

ARC 是从 iOS5出现的编译器新特性,对引用采取自动计数,不再需要手动的对对象进行 retain 和 release,编译器代替我们来做这件事了。

可以通过设置配置文件,在同一个项目中既有 ARC 也有 MRC(例如受老项目或老第三方库影响,需要在 ARC 项目中加入 MRC 的类)。

  • 在 ARC 项目中用到 MRC:在targets的build phases选项下Compile Sources下选择要不使用arc编译的文件,双击,输入 -fno-objc-arc ;   
  • 在 MRC 中用到 ARC:同上步骤,选择要使用arc编译的文件,双击,输入 -fobjc-arc ;

3.1 所有权修饰符

ARC 同 MRC 一样,仍使用引用计数,仍适用1.1中内存管理的4条原则。ARC为何能自动释放呢?关键因素就是—— ARC 中增加了4种所有权修饰符:

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

其中,__strong__weak__autoreleasing对修饰的局部变量初始化为 nil。

以下着重介绍常用的__strong__weak修饰符.

3.1.1 __strong

__strong 是 id 类型、对象类型的默认所有权修饰符,在 ARC 有效时不需要显式写出,如以下两行代码在 ARC 下是相同的:

id obj = [[NSObject alloc] init]; id __strong obj = [[NSObject alloc] init];

__strong 修饰符表示对对象强引用,在超过作用域时废弃,释放所引用的对象及其成员。相当于 MRC 中对该对象调用 release 方法。

__strong在 ARC中是如何实现 MRC的功能的?
对比 MRC,MRC 通过手动写[obj release]来释放自己创建并持有的内存;

ARC 通过增加所有权修饰符这个概念,对 id|对象类型自己创建且持有的对象默认添加__strong 修饰符,从手动写 release 语句变为通过作用域控制对象及其成员的释放。

ARC 对非自己创建但持有的对象,也通过默认添加 __strong修饰符强引用,使其持有(相当于 MRC 中 retain 语句)对象。

回顾 MRC 内存管理的4条原则:

  • 自己生成的对象,自己持有
  • 也可持有非自己生成的对象
  • 释放不再需要自己持有的对象
  • 非自己持有的对象无法释放

『自己生成的对象,自己持有』\『也可持有非自己生成的对象』,ARC 中对 id\对象类型默认添加__strong 修饰符进行强引用;『释放不再需要自己持有的对象』变量作用域结束\成员所属对象废弃\对变量赋值都可以满足这条;『非自己持有的对象无法释放』ARC 中不再需要写 release 语句,因此这条也满足。因此 ARC 也是完全遵守 MRC 内存管理的原则的。

3.1.2 __weak

为解决__strong 导致的循环引用问题,进而造成内存泄露(废弃的对象在超出其生存周期后继续存在),需要引入 _weak 修饰符,对造成循环引用的对象进行弱引用。当作用域结束时,被强引用的对象废弃,弱引用的对象自动被置为 nil。

3.1.3 __unsafe_unretained

iOS4之前用,类似__weak(iOS 5),但需要在使用被其修饰的变量时,先判断是否存在。

3.1.4 __autorelease

autorelease 修饰符同strong 一样一般不显式写出。在 MRC中,通过 NSAutoreleasePool 对象的声明和 drain 代码之间调用 autorelease 进行自动释放。在 ARC中,把需要自动释放的代码写在@autorelease{// code ...}中,当区间内非 alloc\new\copy\mutableCopy\init 的对象会被自动加入 autoreleasepool,在作用域结束的时候释放。如下图:


MRC\ARC的自动释放对比

5. ARC 的规则

  • 不能显式使用 retain\release\retainCount\autorelease
  • 不能使用 NSAllocateObject\NSDeallocateObject
  • 要遵守内存管理的方法命名规则(alloc\new\copy\mutableCopy\ init)
  • 不显式调用 dealloc
  • 使用@autoreleasepool 替代 NSAutoreleasePool
  • 不使用 NSZone
  • 对象型变量不能作为 C 语言结构体成员
  • 显式转换 id 和 void*

来源:简书

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取