memery manager

1、什么是ARC 和 MRC?

MRC(MannulReference Counting) :手工内存管理,需要开发者自己在正确的位置写 retain 和 release

ARC (Automatic Reference Counting):自动内存管理,编译器通过静态分析,在编译时,帮助开发者在正确的位置插入内存管理代码 。

2、什么是引用计数?

引用计数(Reference Count),通过计算引用数量来管理内存的方式。
当我们创建一个对象的时候,他的引用计数是1,当有一个新的指针指向对象的时候,将引用计数+1,当指针不再指向对象的时候,将引用计数-1,当对象的引用计数为0的时候,说明这个对象不再被任何对象引用了,就会被销毁释放,回收内存。

为什么要用引用计数:一般内存管理原则是 “谁申请谁释放”,但是有个问题是,有时候 A 将对象 M 传给 B使用,这时候该由谁来释放呢?
1、A释放,B拷贝一份 然后自己管理,这会导致内存分配释放等操作,浪费资源。
2、B释放,A不释放,这就需要开发者自己去管理这些内存,A申请,B释放十分混乱,当多个地方都需要M的时候会有很多问题,过于复杂。

引用计数非常简单,B使用M就把计数+1,用完之后-1,内存管理完全交给引用计数即可。

3、弱引用的实现原理是什么?

系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。

所以弱引用是有开销的。

4、Core Foundation 对象的内存怎么管理?

Core Foundation对象大多以 XxxCreateWithXxx 来创建
对于这些对象的引用计数的修改,要相应的使用 CFRetain 和 CFRelease 方法。

在 ARC 下,我们有时需要将一个 Core Foundation 对象转换成一个 Objective-C 对象,这个时候我们需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字

__bridge: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。

__bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。

__bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。

4、对象的 reatinCount 属性准确吗?

并不准确,根据苹果官方文档介绍,是不准确的。 链接

There should be no reason to explicitly ask an object what its retain count is (see retainCount). The result is often misleading, as you may be unaware of what framework objects have retained an object in which you are interested. In debugging memory management issues, you should be concerned only with ensuring that your code adheres to the ownership rules.

5、autorelease 对象什么时候释放?

autorelease 使得对象在超出生命周期后能正确的被释放(通过调用release方法)。在调用 release 后,对象会被立即释放,而调用 autorelease 后,对象不会被立即释放,而是注册到 autoreleasepool 中,经过一段时间后 pool结束,此时调用release方法,对象被释放。

当对一个对象调用 autorelease 时,不会立即发生release操作对引用计数-1,而是当这段语句所处的 autoreleasepool 进行 drain 操作时,所有标记了 autorelease 的对象的 retainCount 会被 -1。即 release 消息的发送被延迟到 pool 释放的时候了。

6、自动释放池怎么理解?

当我们需要创建和销毁大量的对象时,使用手动创建的 autoreleasepool 可以有效的避免内存峰值的出现。因为如果不手动创建的话,外层系统创建的 pool 会在整个 runloop circle 结束之后才进行 drain,手动创建的话,会在 block 结束之后就进行 drain 操作。

系统在 runloop 中创建的 autoreleaspool 会在 runloop 一个 event 结束时进行释放操作。我们手动创建的 autoreleasepool 会在 block 执行完成之后进行 drain 操作。需要注意的是:

当 block 以异常(exception)结束时,pool 不会被 drain

Pool 的 drain 操作会把所有标记为 autorelease 的对象的引用计数减一,但是并不意味着这个对象一定会被释放掉

7、main 函数里的那个 autorelase pool 什么时候释放?

UIApplicationMain 函数是整个 app 的入口,用来创建 application 对象(单例)和 application delegate。尽管这个函数有返回值,但是实际上却永远不会返回,当按下 Home 键时,app 只是被切换到了后台状态。

main.m 中的 UIApplicationMain 永远不会返回,只有在系统 kill 掉整个 app 时,系统会把应用占用的内存全部释放出来。

UIApplicationMain 永远不会返回,这里的 autorelease pool 也就永远不会进入到释放那个阶段

假设有些变量真的进入了 main.m 里面这个 pool(没有被更内层的 pool 捕获),那么这些变量实际上就是被泄露的。这个 autorelease pool 等于是把这种泄露情况给隐藏起来了。

UIApplication 自己会创建 main run loop,在 Cocoa 的 runloop 中实际上也是自动包含 autorelease pool 的,因此 main.m 当中的 pool 可以认为是没有必要的。

8、函数返回值什么时候释放?

如果一个函数的返回值是一个对象的指针,那这个对象肯定不能在返回之前直接释放掉
对此,OC 对对象指针的返回值进行了区分,一种叫做 retained return value,另一种叫做 unretained return value。前者表示调用者拥有这个返回值,后者表示调用者不拥有这个返回值,按照“谁拥有谁释放”的原则,对于前者调用者是要负责释放的,对于后者就不需要了。

按照苹果的命名 convention,以 alloc, copy, init, mutableCopy 和 new 这些方法打头的方法,返回的都是 retained return value,例如 [[NSString alloc] initWithFormat:],而其他的则是 unretained return value,例如 [NSString stringWithFormat:]。我们在编写代码时也应该遵守这个 convention。

MRC下 unretained return value 的对象,方法实现要做autorelease操作,因为外边调用者不reatain,他不拥有。所以不用释放。

ARC下 会自动帮我们加好这一切,所以可以直接使用。对于unretained return value ARC会把对象的生命周期延长,但是并不能保证都在自动释放池里,只是根据需要。

9、[NSObject new]与[[NSObject alloc] init] 有什么区别?

[NSObject new]与[[NSObject alloc] init]是完全一致的。

10、autorelease是NSObject的实例方法,NSAutoreleasePool也是继承NSObject的类。那能不能调用autorelease呢?

不能

1
2
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool release];

运行结果发生崩溃。通常在使用Objective-C,也就是Foundation框架时,无论调用哪一个对象的autorelease实例方法,实现上是调用的都是NSObject类的autorelease实例方法。但是对于NSAutoreleasePool类,autorelease实例方法已被该类重载,因此运行时就会出错。

11、强制arc 和 mrc?

在Project里面找到Build Phases-Compile Sources,这里是所有你的编译文件。
指定编译器属性为-fobjc-arc即为该文件使用ARC,
指定编译器属性为-fno-objc-arc即为该文件不使用ARC

12、所有权修饰符是啥?

Objective-C编程中为了处理对象,可将变量类型定义为id类型或各种对象类型。 ARC中,id类型和对象类其类型必须附加所有权修饰符。

strong修饰符 weak修饰符
unsafe_unretained修饰符 autoreleasing修饰符

strong修饰符是id类型和对象类型默认的所有权修饰符。也就是说,不写修饰符的话,默认对象前面被附加了strong所有权修饰符。

1
2
3
id obj = [[NSObject alloc] init];
等同于
id __strong obj = [[NSObject alloc] init];

unsafe_unretained修饰符是不安全的修饰符,尽管ARC式的内存管理是编译器的工作,但附有unsafe_unretained修饰符的变量不属于编译器的内存管理对象。

有时候autoreleasing修饰符要和weak修饰符配合使用。

1
2
3
id __weak obj1 = obj0;

id __autoreleasing tmp = obj1;

复制代码为什么访问附有weak修饰符的变量时必须访问注册到autoreleasepool的对象呢?这是因为weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果把访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。

13、在ARC下,C语言的结构体中可以有 OC对象吗?

对象型变量不能作为C语言结构体(struct、union)的成员

1
2
3
4
//显示警告: ARC forbids Objective-C objects in struct
struct Data {
NSMutableArray *array;
};

在C语言的规约上没有方法来管理结构体成员的生命周期。因为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理对象的生命周期。例如C语言的局部变量可使用该变量的作用域管理对象。但是对于C语言的结构体成员来说,这在标准上就是不可实现的。

要把对象类型添加到结构体成员中,可以强制转换为void *或是附加__unsafe_unretained修饰符。

1
2
3
struct Data {
NSMutableArray __unsafe_unretained *array;
};

__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。

14、可以声明下面这个名字的属性吗?

@property (nonatomic, copy) NSString *newString;
编译器会报错 // 编译器不允许

编译器约定,对于alloc,init,copy,mutableCopy,new这几个家族的方法,后面默认加NS_RETURNS_RETAINED标识;而其他不指名标识的family的方法默认添加NS_RETURNS_NOT_RETAINED标识

参考资料

理解 iOS 的内存管理
Objective-c 内存管理
iOS内存管理详解

Objective-C 中的内存分配