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 时,引用计数-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,在作用域结束的时候释放。如下图:
5. ARC 的规则
- 不能显式使用 retain\release\retainCount\autorelease
- 不能使用 NSAllocateObject\NSDeallocateObject
- 要遵守内存管理的方法命名规则(alloc\new\copy\mutableCopy\ init)
- 不显式调用 dealloc
- 使用@autoreleasepool 替代 NSAutoreleasePool
- 不使用 NSZone
- 对象型变量不能作为 C 语言结构体成员
- 显式转换 id 和 void*
来源:简书
- 本文固定链接: https://zxbcw.cn/post/4694/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)