内存管理(三)AutoreleasePool
前言
本篇接着探索自动释放池的底层原理
准备工作
概念
延迟释放和自动释放池
有些函数、方法需要返回一个对象,而系统可能在该对象被返回之前,就已经销毁了对象。那么为了保证函数、方法返回的对象在被返回之前不被销毁,我们就要使用自动释放池进行延迟销毁(NSAutoreleasePool)
所谓自动释放池,是指它是一个存放对象的容器(集合),而自动释放池会保证延迟销毁该池中所有的对象。出于自动释放池的考虑,所有的对象都应该添加到自动释放池中,这样可以让自动释放池在销毁之前,先销毁池中的所有对象
对象的 autorelease 和 release
autorelease方法,不会改变对象的引用计数,只是将该对象添加到自动释放池中。该方法会返回调用该方法的对象本身
当程序在自动释放池上下文中调用某个对象的autorelease方法时,该方法只是将对象添加到自动释放池中,当该自动释放池释放时,自动释放池会让池中所有的对象执行release方法
自动释放池的销毁和其他普通对象相同,只要其引用计数为0,系统就会自动销毁自动释放池对象。系统会在调用NSAotoreleasePool的dealloc方法时回收该池中的所有对象
AutoreleasePool对象的 drain 和 release
NSAutoreleasePool还提供了一个drain方法来销毁自动释放池中的对象。与release不同,release会使自动释放池自身的引用计数变为0,从而让系统回收NSAutoreleasePool对象,在回收NSAutoreleasePool对象之前,系统会回收该池中的所有对象。而drain方法则只是回收池中的所有对象,并不会销毁自动释放池
运行逻辑
AutoReleasePool是OC的内存自动回收机制,将加入到AutoReleasePool中的变量release时机延迟。在正常情况下,创建的变量会在超出其作用域的时候应该release,但是如果将变量加入AutoreleasePool,那么release将延迟执行,即使超出作用域也不会立即释放,直到runloop休眠或者超出AutoReleasePool作用域才会释放
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QF8XDR5K-1646302649041)(media/16391957881083/25800336-aa25e8b931ffd4c0.webp)]
程序启动到加载完成,主线程对应的Runloop处于休眠状态,直到用户点击交互唤醒Runloop
用户每次交互都会启动一次Runloop用来处理用户的点击、交互事件
Runloop被唤醒后,会自动创建AutoReleasePool,并将所有延迟释放的对象添加到AutoReleasePool
在一次完整的Runloop执行结束前,会自动向AutoReleasePool中的对象发送release消息,然后销毁AutoReleasePool
效果分析
NSString *str1 = [[NSString alloc] initWithFormat:@"hellowwwwwwww"];
汇编
0x102633ec0 <+0>: pushq %rbp
0x102633ec1 <+1>: movq %rsp, %rbp
0x102633ec4 <+4>: subq $0x20, %rsp
0x102633ec8 <+8>: movq %rdi, -0x8(%rbp)
0x102633ecc <+12>: movq %rsi, -0x10(%rbp)
0x102633ed0 <+16>: movq 0x74e1(%rip), %rax ; (void *)0x00007fff8026c0f8: NSString
0x102633ed7 <+23>: movq %rax, %rdi
0x102633eda <+26>: callq 0x102634450 ; symbol stub for: objc_alloc
0x102633edf <+31>: leaq 0x2132(%rip), %rcx ; @"hellowwwwwwww"
0x102633ee6 <+38>: movq 0x74b3(%rip), %rsi ; "initWithFormat:"
0x102633eed <+45>: movq %rax, %rdi
0x102633ef0 <+48>: movq %rcx, %rdx
0x102633ef3 <+51>: movb $0x0, %al
0x102633ef5 <+53>: callq *0x2105(%rip) ; (void *)0x00007fff20175280: objc_msgSend
0x102633efb <+59>: xorl %r8d, %r8d
0x102633efe <+62>: movl %r8d, %esi
0x102633f01 <+65>: movq %rax, -0x18(%rbp)
-> 0x102633f05 <+69>: leaq -0x18(%rbp), %rax
0x102633f09 <+73>: movq %rax, %rdi
0x102633f0c <+76>: callq 0x10263447a ; symbol stub for: objc_storeStrong
0x102633f11 <+81>: addq $0x20, %rsp
0x102633f15 <+85>: popq %rbp
0x102633f16 <+86>: retq
就是标准的对象创建过程
NSString *str2 = [NSString stringWithFormat:@"hellowwwwwwww auto relase..."];
0x107692eb0 <+0>: pushq %rbp
0x107692eb1 <+1>: movq %rsp, %rbp
0x107692eb4 <+4>: subq $0x20, %rsp
0x107692eb8 <+8>: leaq 0x2159(%rip), %rax ; @"hellowwwwwwww auto relase..."
0x107692ebf <+15>: movq %rdi, -0x8(%rbp)
0x107692ec3 <+19>: movq %rsi, -0x10(%rbp)
-> 0x107692ec7 <+23>: movq 0x74ea(%rip), %rcx ; (void *)0x00007fff8026c0f8: NSString
0x107692ece <+30>: movq 0x74cb(%rip), %rsi ; "stringWithFormat:"
0x107692ed5 <+37>: movq %rcx, %rdi
0x107692ed8 <+40>: movq %rax, %rdx
0x107692edb <+43>: movb $0x0, %al
0x107692edd <+45>: callq *0x211d(%rip) ; (void *)0x00007fff20175280: objc_msgSend
0x107692ee3 <+51>: movq %rax, %rdi
0x107692ee6 <+54>: callq 0x107693464 ; symbol stub for: objc_retainAutoreleasedReturnValue
0x107692eeb <+59>: xorl %r8d, %r8d
0x107692eee <+62>: movl %r8d, %esi
0x107692ef1 <+65>: movq %rax, -0x18(%rbp)
0x107692ef5 <+69>: leaq -0x18(%rbp), %rax
0x107692ef9 <+73>: movq %rax, %rdi
0x107692efc <+76>: callq 0x10769346a ; symbol stub for: objc_storeStrong
0x107692f01 <+81>: addq $0x20, %rsp
0x107692f05 <+85>: popq %rbp
0x107692f06 <+86>: retq
使用stringWith的方式,字符串在内部会被设置成autorelease,不用手动释放,系统会回收,因此将会在最近的一个自动释放池drain或release时被回收
例子1
__weak NSString *weakStr;
__weak NSString *weakStrAutoRelease;
@implementation ViewController
- (void)test1 {
NSString *str1 = [[NSString alloc] initWithFormat:@"hellowwwwwwww"];
weakStr = str1;
NSString *str2 = [NSString stringWithFormat:@"hellowwwwwwww auto relase..."];
weakStrAutoRelease = str2;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self test1];
NSLog(@"%s中weakStr== %@", __func__, weakStr);
NSLog(@"%s中weakAutoStr==%@", __func__, weakStrAutoRelease);
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"%s中weakStr== %@", __func__, weakStr);
NSLog(@"%s中weakAutoStr==%@", __func__, weakStrAutoRelease);
}
- (void) viewDidAppear:(BOOL)animated {
NSLog(@"%s中weakStr== %@", __func__, weakStr);
NSLog(@"%s中weakAutoStr==%@", __func__, weakStrAutoRelease);
}
输出
-[ViewController viewDidLoad]中weakStr== (null)
-[ViewController viewDidLoad]中weakAutoStr==hellowwwwwwww auto relase...
-[ViewController viewWillAppear:]中weakStr== (null)
-[ViewController viewWillAppear:]中weakAutoStr==hellowwwwwwww auto relase...
-[ViewController viewDidAppear:]中weakStr== (null)
-[ViewController viewDidAppear:]中weakAutoStr==(null)
分析
-
使用方式1创建的字符串str1,在test1方法执行完成后就会释放,弱引用weakStr也会释放掉。所以weakStr打印结果都是空
-
使用方式2创建的对象str2,这个对象被添加到了当前的autoreleasepool中,起到了延迟释放的效果。这个对象是一个autoreleased对象,autoreleased对象是被添加到了当前最近的autoreleasepool中,只有当这个autoreleasepool自身drain/release 的时候,autoreleasepool中的autoreleased对象才会被release。
-
对象weakStrAutoRelease,在viewWillAppear中依然存在在viewDidAppear中成了null,那么这个对象一定是在viewWillAppear和viewDidAppear方法之间的某个时候被释放了,并且是由于它所在的autoreleasepool被release的时候释放的。
我们可以在lldb调试中设置观察点,来查看对象的释放过程,如下:
设置断点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O828nvfv-1646302649044)(media/16391957881083/WechatIMG45.jpeg)]
设置 watchpoint
watchpoint set v weakStrAutoRelease
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U3mjx92q-1646302649045)(media/16391957881083/WechatIMG43.jpeg)]
在运行栈中可以发现,weakStrAutoRelease对象在自动释放池释放时完成了释放
手动添加一个@autoreleasepool
例子2
代码中手动添加了一个@autoreleasepool,在自动释放池内,weakStrAutoRelease一直不会释放,而出了自动释放池就会释放
__weak NSString *weakStr;
__weak NSString *weakStrAutoRelease;
@implementation ViewController
- (void)test1 {
NSString *str1 = [[NSString alloc] initWithFormat:@"hellowwwwwwww"];
weakStr = str1;
NSString *str2 = [NSString stringWithFormat:@"hellowwwwwwww auto relase..."];
weakStrAutoRelease = str2;
}
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
[self test1];
NSLog(@"pool中weakStr== %@", weakStr);
NSLog(@"pool中weakAutoStr==%@", weakStrAutoRelease);
}
NSLog(@"pool外weakStr== %@", weakStr);
NSLog(@"pool外weakAutoStr==%@", weakStrAutoRelease);
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"%s中weakStr== %@", __func__, weakStr);
NSLog(@"%s中weakAutoStr==%@", __func__, weakStrAutoRelease);
}
- (void) viewDidAppear:(BOOL)animated {
NSLog(@"%s中weakStr== %@", __func__, weakStr);
NSLog(@"%s中weakAutoStr==%@", __func__, weakStrAutoRelease);
}
输出
pool中weakStr== (null)
pool中weakAutoStr==hellowwwwwwww auto relase...
pool外weakStr== (null)
pool外weakAutoStr==(null)
-[ViewController viewWillAppear:]中weakStr== (null)
-[ViewController viewWillAppear:]中weakAutoStr==(null)
-[ViewController viewDidAppear:]中weakStr== (null)
-[ViewController viewDidAppear:]中weakAutoStr==(null)
明显看出使用方式2创建的对象weakStrAutoRelease在自动释放池内都能够正常使用,出了自动释放池就会被释放,起到延迟释放的效果。
但是使用方式1创建的字符串weakStr,为什么在自动释放池内就释放了呢?他不会加入到自动释放池吗?带着疑问继续往下走!!
自动释放池原理分析
查找原理
代码
- (void)test2 {
@autoreleasepool {
NSString *str2 = [NSString stringWithFormat:@"hellowwwwwwww auto relase..."];
}
}
clang
static void _I_ViewController_test2(ViewController * self, SEL _cmd) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSString *str2 = ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_ViewController_2e1048_mi_2);
}
}
@autoreleasepool在编译后变成了以下代码:
__AtAutoreleasePool __autoreleasepool;
main.cpp文件中全局搜索__AtAutoreleasePool的定义,找到__AtAutoreleasePool结构体的定义,如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
该结构体提供了一个构造函数objc_autoreleasePoolPush和一个析构函数objc_autoreleasePoolPop。所以自动释放池在底层其实是一个结构体,其通过objc_autoreleasePoolPush完成自动释放池的创建,objc_autoreleasePoolPop来释放自动释放池。
按照C++语法来讲 __AtAutoreleasePool __autoreleasepool 会触发析构函数
我们设置objc_autoreleasePoolPush的符号断点 验证一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1buhSJgD-1646302649047)(media/16391957881083/16393005891383.png)]
运行项目 的确能够进入 汇编如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZSghlcc-1646302649048)(media/16391957881083/WechatIMG46%202.jpeg)]
同时我们看到自动释放池的实现源码在libobjc.A.dylib库
结构分析
下面进入源码分析
跟踪objc_autoreleasePoolPush的方法实现 如下
void *
_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
其调用了objc_autoreleasePoolPush()方法,继续跟踪代码
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
其调用了AutoreleasePoolPage的push方法
AutoreleasePoolPage
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HiFsaKM4-1646302649049)(media/16391957881083/471639301104_.pic_hd.jpg)]
注解分析:
- 一个线程的自动释放池是一堆指针
- 每个指针要么是一个要释放的对象,要么是POOL_BOUNDARY(自动释放池边界-哨兵对象)
- 堆栈被分成一个双向链接的页面列表, 页面已添加对象并根据需要删除
- 线程本地存储指向新自动释放的热点页面对象被存储
AutoreleasePoolPage继承于AutoreleasePoolPageData,那么查看AutoreleasePoolPageData的结构如下:
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct AutoreleasePoolEntry {
uintptr_t ptr: 48;
uintptr_t count: 16;
static const uintptr_t maxCount = 65535; // 2^16 - 1
};
static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
属性
- magic⽤来校验AutoreleasePoolPage的结构是否完整
- next 指向最新添加的autoreleased对象的下⼀个位置,初始化时指向begin()
- thread 指向当前线程
- parent指向⽗结点,第⼀个结点的parent值为nil
- child 指向⼦结点,最后⼀个结点的child值为nil
- depth 代表深度,从0开始,往后递增1
- hiwat代表high water mark最⼤⼊栈数量标记
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
跟踪push()方法
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
在非debug模式下首先调用autoreleaseFast方法,并传入边界对象(哨兵对象)。查看autoreleaseFast实现源码,如下:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
分析
首先获取当前hotPage,如果不为空且没有满,则会向该页中添加obj;
如果该页已满,则调用autoreleaseFullPage方法;
如果当前hotPage不存在,也就是没有page,则调用autoreleaseNoPage方法
autoreleaseNoPage实现源码
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
ASSERT(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
// 1、创建第一页 并把第一页设置成hotpage当前页
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
// 2、添加哨兵对象 边界对象
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
// 3、往page中加obj
return page->add(obj);
}
分析
1、创建第一页 并把第一页设置成hotpage当前页
2、添加哨兵对象 边界对象
3、往page中加obj
查看 AutoreleasePoolPage 结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RbMeQFso-1646302649050)(media/16391957881083/481639301656_.pic_hd.jpg)]
通过调用AutoreleasePoolPageData的构造函数实现初始化,并确定页之间的链表关系。
通过上面的结构我们可以确定AutoreleasePoolPageData属性占56个字节。见下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jEtyZWlW-1646302649050)(media/16391957881083/25800336-8237ed352bb24df0.webp)]
因为页中next字段用于设置存储obj的位置,那么因为每个页自身有一些属性需要占用一部分空间,所以next的起始值是page首地址平移56个字节,也就是构造函数中begin()方法所确定下来的值。
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
汇编
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M4bwS1US-1646302649051)(media/16391957881083/491639302398_.pic.jpg)]
16进制相差38 转化为10进制 就是 56
如果页满时,就会调用上面的autoreleaseFullPage方法,见下面实现源码
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
分析
do…while循环找到最后一个page,如果page没有满,就将page设置为hotPage。如果page已满,则会新建一个page,也将page设置为hotPage。最后往page中添加obj。
综合上面的数据结构和源码实现,我们可以得出以下结论:
- Autoreleasepool是由多个AutoreleasePoolPage以双向链表的形式连接起来的
- Autoreleasepool的基本原理:在自动释放池创建的时候,会在当前的AutoreleasePoolPage中设置一个标记位(边界),在此期间,当有对象调用autorelease时,会把对象添加到AutoreleasePoolPage中
- 如果当前页加满了,会初始化一个新页,然后用双向链表链接起来,并把初始化的一页设置为hotPage,当自动释放池pop时,从最下面依次往上pop,调用每个对象的release方法,直到遇到标志位
如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EInf5gK6-1646302649052)(media/16391957881083/25800336-3b662e65327a9199.webp)]
满页临界值
自动释放池一页能够存储多少个对象呢?如果能够打印输出自动释放池的数据,会更便于我们对自动释放池的了解。在源码中也提供了相关的打印数据结构的方法
void
_objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
printAll()方法如下:
__attribute__((noinline, cold))
static void printAll()
{
_objc_inform("##############");
_objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());
AutoreleasePoolPage *page;
ptrdiff_t objects = 0;
for (page = coldPage(); page; page = page->child) {
objects += page->next - page->begin();
}
_objc_inform("%llu releases pending.", (unsigned long long)objects);
if (haveEmptyPoolPlaceholder()) {
_objc_inform("[%p] ................ PAGE (placeholder)",
EMPTY_POOL_PLACEHOLDER);
_objc_inform("[%p] ################ POOL (placeholder)",
EMPTY_POOL_PLACEHOLDER);
}
else {
for (page = coldPage(); page; page = page->child) {
page->print();
}
}
_objc_inform("##############");
}
创建一个案例查看其内部存储结构,如下
例子一
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
for (int i = 0; i < 4 ; i++) {
NSObject *yang = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
输出
objc[94856]: ##############
objc[94856]: AUTORELEASE POOLS for thread 0x11263fe00
objc[94856]: 5 releases pending.
objc[94856]: [0x7fa26c818000] ................ PAGE (hot) (cold)
objc[94856]: [0x7fa26c818038] ################ POOL 0x7fa26c818038
objc[94856]: [0x7fa26c818040] 0x600000d40170 NSObject
objc[94856]: [0x7fa26c818048] 0x600000d40180 NSObject
objc[94856]: [0x7fa26c818050] 0x600000d40190 NSObject
objc[94856]: [0x7fa26c818058] 0x600000d401a0 NSObject
objc[94856]: ##############
分析
- 当前自动释放池起始页是0x7fa26c818000 一共1页 所以第一页也即是hotpage
- 地址平移56个字节后放入的是哨兵对象,哨兵对象地址为0x10380a038
- 紧接着放入4个NSObject对象
那么一页能放多少呢?源码中也有定义,如下:
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MIN_SIZE; // size and alignment, power of 2
#endif
#define PAGE_MIN_SHIFT 12
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
通过以上的源码定义发现其大小为1<<12,也即是4096,而每页自身属性的占用56个字节,同时第一页需要一个哨兵对象8个字节,所以首页最多可以放(4096 - 56 - 8) / 8 = 504个对象
代码验证
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
for (int i = 0; i < 505 ; i++) {
NSObject *yang = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
输出
首页full
第二页 1个对象 第二页为当前页
objc[96726]: [0x7fdb14014000] ................ PAGE (full) (cold)
objc[96726]: [0x7fdb14014038] ################ POOL 0x7fdb14014038
objc[96726]: [0x7fdb14014040] 0x60000042c0c0 NSObject
.....
.....
.....
objc[96726]: [0x7fdb14014ff8] 0x60000042e030 NSObject
objc[96726]: [0x7fdb1400a000] ................ PAGE (hot)
objc[96726]: [0x7fdb1400a038] 0x60000042e040 NSObject
objc[96726]: ##############
通过输出自动释放池的数据结构可以发现,当放入505个对象时,会新开辟一页,并且第二页中只有一个对象。(哨兵对象只会放在第一页)所以第一页最多可以放504个对象,之后每页可以存储505个对象。
自动释放池注意点
释放和销毁
对象release 只是放弃一次所有权有权,引用计数-1,而真正的销毁是 引用计数为0 系统会调用delloc方法 delloc内部调用走一大堆逻辑 最后到free函数 free函数进行堆内存回收,对象才是真的销毁。
Person *yang1 = [[Person alloc] init];
Person *yang2 = yang1;
Person *yang3 = yang1;
NSLog(@"yang1=%ld", CFGetRetainCount((__bridge CFTypeRef)yang1));
@autoreleasepool {
Person *yang4 = yang1;
NSLog(@"pool内 yang1=%ld", CFGetRetainCount((__bridge CFTypeRef)yang1));
}
NSLog(@"pool外 yang1=%ld", CFGetRetainCount((__bridge CFTypeRef)yang1));
输出
yang1=3
pool内 yang1=4
pool外 yang1=3
当自动释放池结束的时候,仅仅是对存储在自动释放池中的对象发送1条release消息,而不是销毁对象。
free函数销毁对象之后 内存地址中对象的数据并没有发生改变
销毁对象只是收回 对象内存空间的使用权,里面存的数据一直到下一次 一个新的对象使用该内存区域时候才会覆盖。
MRC
@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@end
@implementation Person
- (void)dealloc {
NSLog(@"%s",__func__);
[super dealloc];
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self test5];
}
- (void)test5 {
Person *yang1 = [[Person alloc] init];
yang1.name = @"yangyang";
[yang1 release];
NSLog(@"yang1销毁之后继续查看内存");
}
输出
(lldb) p yang1
(Person *) $0 = 0x0000600002b3c340
(lldb) p &yang1
(Person **) $1 = 0x00007ffee4d5cfb8
(lldb) x/5gx 0x00007ffee4d5cfb8
0x7ffee4d5cfb8: 0x0000600002b3c340 0x000000010aea150d
0x7ffee4d5cfc8: 0x00007ff59cc08be0 0x00007ffee4d5d000
0x7ffee4d5cfd8: 0x000000010aea10b7
-[Person dealloc]
yang1销毁之后继续查看内存
(lldb) x/5gx 0x00007ffee4d5cfb8
0x7ffee4d5cfb8: 0x0000600002b3c340 0x000000010aea150d
0x7ffee4d5cfc8: 0x00007ff59cc08be0 0x00007ffee4d5d000
0x7ffee4d5cfd8: 0x000000010aea10b7
(lldb)
自动释放池的嵌套
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSLog(@"开始");
NSObject *yang = [[[NSObject alloc] init] autorelease];
NSLog(@"%@",yang);
_objc_autoreleasePoolPrint();
@autoreleasepool {
NSObject *yang = [[[NSObject alloc] init] autorelease];
NSLog(@"%@",yang);
_objc_autoreleasePoolPrint();
}
NSLog(@"出小自动释放池");
_objc_autoreleasePoolPrint();
}
return 0;
}
输出
2021-12-12 18:48:34.741297+0800 03_autorelasepool_MRC[9127:12790223] 开始
2021-12-12 18:48:34.741846+0800 03_autorelasepool_MRC[9127:12790223] <NSObject: 0x600000c28000>
objc[9127]: ##############
objc[9127]: AUTORELEASE POOLS for thread 0x103259e00
objc[9127]: 2 releases pending.
objc[9127]: [0x7fadf2009000] ................ PAGE (hot) (cold)
objc[9127]: [0x7fadf2009038] ################ POOL 0x7fadf2009038
objc[9127]: [0x7fadf2009040] 0x600000c28000 NSObject
objc[9127]: ##############
2021-12-12 18:48:34.742233+0800 03_autorelasepool_MRC[9127:12790223] <NSObject: 0x600000c340e0>
objc[9127]: ##############
objc[9127]: AUTORELEASE POOLS for thread 0x103259e00
objc[9127]: 4 releases pending.
objc[9127]: [0x7fadf2009000] ................ PAGE (hot) (cold)
objc[9127]: [0x7fadf2009038] ################ POOL 0x7fadf2009038
objc[9127]: [0x7fadf2009040] 0x600000c28000 NSObject
objc[9127]: [0x7fadf2009048] ################ POOL 0x7fadf2009048
objc[9127]: [0x7fadf2009050] 0x600000c340e0 NSObject
objc[9127]: ##############
2021-12-12 18:48:34.742634+0800 03_autorelasepool_MRC[9127:12790223] 出小自动释放池
objc[9127]: ##############
objc[9127]: AUTORELEASE POOLS for thread 0x103259e00
objc[9127]: 2 releases pending.
objc[9127]: [0x7fadf2009000] ................ PAGE (hot) (cold)
objc[9127]: [0x7fadf2009038] ################ POOL 0x7fadf2009038
objc[9127]: [0x7fadf2009040] 0x600000c28000 NSObject
objc[9127]: ##############
自动释放池嵌套并不会影响数据结构,只是多插入一个哨兵对象
哪些对象可以放入自动释放池
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"开始");
NSObject *yang1 = [[NSObject alloc] init];
NSLog(@"%@",yang1);
NSObject *yang2 = [[[NSObject alloc] init] autorelease];
NSLog(@"%@",yang2);
NSString *str1 = [[NSString alloc] initWithFormat:@"hello yangyang"];
NSLog(@"%p",str1);
NSString *str2 = [NSString stringWithFormat:@"yangyang hello"];
NSLog(@"%p",str2);
_objc_autoreleasePoolPrint();
}
return 0;
}
输出
2021-12-12 18:53:20.564320+0800 03_autorelasepool_MRC[10479:12798259] 开始
2021-12-12 18:53:20.564810+0800 03_autorelasepool_MRC[10479:12798259] <NSObject: 0x600002a40170>
2021-12-12 18:53:20.564932+0800 03_autorelasepool_MRC[10479:12798259] <NSObject: 0x600002a48000>
2021-12-12 18:53:20.565058+0800 03_autorelasepool_MRC[10479:12798259] 0x600002840340
2021-12-12 18:53:20.565139+0800 03_autorelasepool_MRC[10479:12798259] 0x6000026405a0
objc[10479]: ##############
objc[10479]: AUTORELEASE POOLS for thread 0x11ce25e00
objc[10479]: 3 releases pending.
objc[10479]: [0x7f9c9400c000] ................ PAGE (hot) (cold)
objc[10479]: [0x7f9c9400c038] ################ POOL 0x7f9c9400c038
objc[10479]: [0x7f9c9400c040] 0x600002a48000 NSObject
objc[10479]: [0x7f9c9400c048] 0x6000026405a0 __NSCFString
objc[10479]: ##############
分析
用alloc, init,copy等方法创建的对象,这些我们自己持有的,我们想让他延迟释放,就调用autorelase方法,这样在自动释放池出栈的时候,对象就会释放掉 注意释放只是发送一条release消息 对象销毁不销毁 要看对象的引用计数此刻是不是0
stringWithFormt 这种创建不归调用者持有的,内部会自动加入到自动释放池中(如果字符串很短 会被优化为 NSTaggedPointerString 也就是TiggedPointer小对象 不用引用计数来管理。 )