内存管理(三)AutoreleasePool

内存管理(三)AutoreleasePool

前言

本篇接着探索自动释放池的底层原理

准备工作

Objc-818.2

概念

延迟释放和自动释放池

有些函数、方法需要返回一个对象,而系统可能在该对象被返回之前,就已经销毁了对象。那么为了保证函数、方法返回的对象在被返回之前不被销毁,我们就要使用自动释放池进行延迟销毁(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小对象 不用引用计数来管理。 )

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值