高清图片请私信我
ARC & MRC
相同点:都需要通过添加retain、release、autorelease等控制引用计数的加减
不同点:ARC 是通过LLVM编译器和Runtime在合适的地方添加
release & autorelease
相同点
- 都会对对象的引用计数实现-1操作
- 在ARC环境下都被不能显示添加
不同点:
- release:立即-1,对象引用计数为0时,立即释放(销毁)
- autoRelease:系统在恰当的时候给对象进行release,延迟释放
创建方式
MRC
- NSAutoreleasePool
- @autoreleasepool{}
ARC
- 只能使用@autoreleasepool{}
原理@autoreleasepool{}
__AtAutoreleasePool:
底层为C++的__AtAutoreleasePool类型的结构体
-
结构体构造函数
-
调用
objc_autoreleasePoolPush(),返回atautoreleasepoolobj- // NSObject.mm
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
- // NSObject.mm
-
-
结构体析构函数
-
调用objc_autoreleasePoolPop()函数,并将atautoreleasepoolobj传入。
- // NSObject.mm
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
- // NSObject.mm
-
AutoreleasePoolPage:
构造和析构都是通过AutoreleasePoolPage对象实现的push() 和 pop方法实现的
-
ARP是多个page结构,以栈为节点,通过
双向链表的形式组合而成 -
每个page的大小:
4096字节,成员变量7个,占用56字节,其余4040以栈结构方式存储autorelease对象的地址 -
autorelease对象从高地址向低地址进行存储,并且以POOL_BOUNDARY哨兵对象开头进行存储,其实用来区分不同的ARP,特别是嵌套的时候-
POOL_BOUNDARY
哨兵对象或边界对象-
用来区分不同的
ARP,解决ARP的嵌套问题,而不是Page,可以理解为其实ARP的唯一性标志 -
每创建一个
ARP,就会调用push(),
将POOL_BOUNDARY入栈,并返回其存放的内存地址,
这个地址可以理解为是当前栈对应的下一个位置;-
当ARP添加autorelease对象时,
将对象的地址入栈,对象前面至少有一个POOL_BOUNDARY -
push方法
-
创建ARP的时候调用,方法内部调用
autoreleaseFast(POOL_BOUNDARY) -
autoreleaseFast的实现
-
调用hotPage()方法获得未满的Page
在autoreleaseFast知心过程中,
可能出现的情况-
Page存在,未满
-
通过
page->add(obj)将autorelease对象入栈- page->add(obj):将autorelease对象添加到Page中的next指针所指向的位置,
并将next指针指向这个对象的下一个位置,
然后将该对象的位置返回。
- page->add(obj):将autorelease对象添加到Page中的next指针所指向的位置,
-
-
Page存在,已满
-
autoreleaseFullPage(obj, page)
创建一个新的Page,并将autorelease对象入栈-
通过while循环,结合page的child指针直接找到最后一个page
-
page未满
-
page已满
- 新建一个page,设置为hotPage,然后添加对象
-
-
-
-
Page不存在
-
autoreleaseNoPage(obj)
创建第一个Page并将autorelease对象添加进去- 1.判断是否有空的ARP(理论上是没有),
如果没有,调用setEmptyPoolPlaceholder()
生成一个占位符,表示一个空ARP - 2.创建第一个Page
- 3.添加POOL_BOUNDARY,
返回POOL_BOUNDARY的下一个位置
- 1.判断是否有空的ARP(理论上是没有),
-
-
-
-
-
-
当ARP销毁的时候,调用pop()方法,
并传入一个POOL_BOUNDARY,会从ARP中的最后一个对象开始,依次发送release消息,直到遇到POOL_BOUNDARY-
pop(token)方法
-
简单概括:ARP销毁的时候,调用pop()方法,实际是给ARP内的入栈的autorelease对象依次放release消息,直到遇到POOL_BOUNDARY
-
执行过程
-
1.判断传入token是不是
EMPTY_POOL_PLACEHOLDER- 是,则清空这个ARP
- 不是,转到下面第2
-
2.通过pageForPointer(token)拿到token所在的Page,及ARP的第一个page
-
3.通过page->releaseUntil(stop),将ARP中的autorelease对象全部释放(发release消息)
stop:就是POOL_BOUNDARY- 通过while循环遍历,
从最后一个入栈的autorelease对象开始
依次发送release消息
直到遇到POOL_BOUNDARY
- 通过while循环遍历,
-
4.判断page是否有子Page,有就销毁 ??有疑问
-
-
page->releaseUntil(token)
- 通过while循环,从最后一个入栈的autorelease对象开始,依次销毁(发送release),直到遇到POOL_BOUNDARY
-
-
-
-
-
AutoreleasePoolPage创建
- 1.参数为parentPage,即上一层page的地址,猜想,第一个page的parentPage为nil
- 2.page的depth +1
- 3.page的next指针指向bagin()
- 4.page的parent指针指向参数parentPage
page的child指针执行自己
形成闭环双链表
-
begin、end、empty、full
- begin:Page自己的地址+Page对象的大小56个字节;
- end: Page自己的地址+4096个字节
- empty:next指针指向地址 == begin
- full: next指针指向地址 = end
对象在调用autorelease方法
通过调用autoreleaseFast()添加进page
查看ARP情况
1.声明函数:
extern void _objc_autoreleasePoolPrint(void);
在需要的地方调用此函数
与Runloop的关系
概括
- 循环开始时,主线程创建一个ARP
- 循环结束时,销毁ARP,
并释放ARP中的所有autorelease对象
主线程的RunLoop注册了2个observer
-
第1个监听 kCFRunLoopEntry事件,
自动创建__AtAutoreleasePool,
调用objc_autoreleasePoolPush() -
第2个监听
-
kCFRunLoopBeforeWaiting
即将休眠- 销毁1监听中的__AtAutoreleasePool,
并调用objc_autoreleasePoolPop() - 重新创建一个__AtAutoreleasePool,
并调用objc_autoreleasePoolPush()
- 销毁1监听中的__AtAutoreleasePool,
-
kCFRunLoopBeforeExit
即将退出RunLoop- 销毁休眠前创建的 __AtAutoreleasePool,
并调用objc_autoreleasePoolPop()
- 销毁休眠前创建的 __AtAutoreleasePool,
-
ARC先autorelease对象什么时候释放
系统干预:
1.RunLoop线程休眠,2.RunLoop线程销毁
人工干预:
手动添加autoreleasePool,在大括号结束后销毁
ARC 环境下,需不需要手动添加
@autoreleasepool
AppKit 和 UIKit 框架会在RunLoop每次事件循环迭代中创建并处理@autoreleasepool,无需自动创建
手动添加@autoreleasepool来管理这些对象可以很大程度地减少内存峰值
苹果建议的手动添加的情况
- 如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
- 如果你编写的循环中创建了大量的临时对象;
你可以在循环内使用@autoreleasepool在下一次迭代之前处理这些对象。
在循环中使用@autoreleasepool有助于减少应用程序的最大内存占用。 - 如果你创建了辅助线程。
一旦线程开始执行,就必须创建自己的@autoreleasepool;否则,你的应用程序将存在内存泄漏。
\