MRC
使用ARC,开发者不再需要手动的retain/release/autorelease。编译器会自动插入对应的代码,再结合Objective C的runtime,实现自动引用计数。一般来说,对象的alloc、retain操作都会让引用计数+1,release则会让引用计数-1。
注意:对于某些系统类,使用alloc方法是不一定的让引用计数+1。
因为这些类的alloc方法并非简单的申请一块内存。
NSArray *arr = [[NSArray alloc] init];
NSLog(@"arr retainCount =%ld",arr.retainCount);
NSObject * obj = [[NSObject alloc] init];
[obj retain];
NSString *str = @"test";//字符串常量系统不会收回,也不会对其作引用计数
NSLog(@"str retainCount =%ld",str.retainCount);
NSLog(@"obj retainCount =%ld",obj.retainCount);
输出的结果是
2018-05-14 14:17:02.687785+0800 TestMRC[91989:4598336] arr retainCount =-1
2018-05-14 14:17:02.687928+0800 TestMRC[91989:4598336] str retainCount =-1
2018-05-14 14:17:02.688025+0800 TestMRC[91989:4598336] obj retainCount =2
为什么使用了alloc还要使用init方法呢?因为内存中有很多数据并没有清零,所以需要init方法初始化一下。
autorelease
在MRC中,对某一个对象调用autorelease方法,表示延迟发送release消息
,意思指的是,当我们把一个对象标记为autorelease时:[obj autorelease];
,表示现在暂时不进行release操作,等这段语句所处的 autoreleasepool 进行 drain 操作时,所有标记了 autorelease 的对象的 retainCount 会被 -1。即 release 消息的发送被延迟到 pool 释放的时候了。
在ARC中,我们并不需要手动调用 autorelease 有关的方法,就可以正确管理好内存。在OC中,主线程与子线程的每个Runloop已经默认会创建一个autoreleasepool,在一个Runloop迭代结束的时候,完成释放池中的变量释放。所以没有手加Autorelease Pool的情况下,MRC中的Autorelease对象是在当前的Runloop迭代结束时释放。
一般来说,当需要创建和销毁大量的对象时,使用手动创建的 autoreleasepool 可以有效的避免内存峰值的出现。因为如果不手动创建的话,外层系统创建的 pool 会在整个 runloop 迭代 结束之后才进行 释放,手动创建的话,会在 block 结束之后(也就是大括号作用域结束)就进行 释放 操作。
一个普遍被使用的例子如下:
for (int i = 0; i < 100000000; i++)
{
@autoreleasepool
{
NSString* string = @"ab c";
NSArray* array = [string componentsSeparatedByString:string];
}
}
如果不使用 autoreleasepool ,需要在循环结束之后释放 100000000 个字符串,如果使用的话,则会在每次循环结束的时候都进行 release 操作。
OC中,数组的enumerate方法内部其实已经自动创建了autoreleasepool, for循环是没有自动创建autoreleasepool的,需要手动创建。
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// OC自动创建@autoreleasepool
}];
AutoreleasePoolPage
ARC下,我们使用@autoreleasepool{}
来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类,它在OC定义如下。
class AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
-
AutoreleasePool由若干个AutoreleasePoolPage以双向链表的形式组合而成(结构中的parent指针指向前一个Page位置,child指针指向后一个Page位置)
- AutoreleasePoolPage每个对象为4096字节内存(对应iOS系统的虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
- 上面的
id *next
指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置 - 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入。
简单来说
向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage栈中,并由此page的栈顶next指针标记位置,每次调用objc_autoreleasePoolPush
add对象时,next指针从Page栈的底部依次往顶部移动。
参考文章
https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/MM.html
https://draveness.me/autoreleasepool/
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/