【iOS】autoreleasepool

Let’s talk about what we’ve learned recently autoreleasepool. We may have written a lot of stupid code in our daily life, which has many flaws, but we may have learned it relatively little at the time and didn’t understand it well, like the following:

for (int i = 0; i < 1000000; i++) {
    
    
    NSNumber *num = [NSNumber numberWithInt:i];
}

一、@autoreleasepool{}

When we usually create mainthe code of a function, we will find that there is this thing in it @autoreleasepool{}. After using clangcompilation: @autoreleasepool{...}it is compiled into {__AtAutoreleasePool __autoreleasepool; ... }.

__AtAutoreleasePoolWhat exactly is this ?

It is actually a structure. Objc_autoreleasePoolPush(void) is called when creating the __AtAutoreleasePool structure variable. When it is destroyed, objc_autoreleasePoolPop(void *) is called, that is, its constructor and destructor, so we can see that it is actually It is an autorelease pool variable encapsulated in C++. It will add the content in {} in @autoreleasepool{…} to the autorelease pool to facilitate memory management.

But it mainseems to be of no use in this function, because the memory will be released when the program ends, so why is it added here @autoreleasepool{}?

It is technically feasible. It does not matter if you remove @autoreleasepool{} in the main function. However, for the sake of rigor, in order to make the autorelease object created by UIApplicationMin have an autorelease pool that can be added and can be released when the autorelease pool ends. The object does not rely on the recycling of the operating system, so with @autoreleasepool{}, it can be understood as the outermost automatic release pool that triggers the release mechanism.

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
    
    
  __AtAutoreleasePool() {
    
    atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {
    
    objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

So what we usually ARCwrite in the environment @autoreleasepool{}is actually a simple use of automatic release pool creation and release.

Insert image description here

It will { create an auto-release pool at , destroy the auto-release pool at } and issue releasea notification, allowing the variables in it to releaseoperate on their own.

二、AutoreleasePoolPage

From above __AtAutoreleasePoolwe can see the objc_autoreleasePoolPushsum of these two methods objc_autoreleasePoolPop, but what exactly are these?

void *objc_autoreleasePoolPush(void) {
    
    
    return AutoreleasePoolPage::push();
}
 
void objc_autoreleasePoolPop(void *ctxt) {
    
    
    AutoreleasePoolPage::pop(ctxt);
}

We can see that a new class has been introduced here AutoreleasePoolPage, and its relevant source code is as follows:

class AutoreleasePoolPage {
    
    
    magic_t const magic;//AutoreleasePoolPage 完整性校验
    id *next;//下一个存放autorelease对象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的线程
    AutoreleasePoolPage * const parent;//父节点
    AutoreleasePoolPage *child;//子节点
    uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
    uint32_t hiwat;
}

Each autorelease pool is AutoreleasePoolPagecomposed of a series, and AutoreleasePoolPagethe size of each is 4096 bytes (hexadecimal 0x1000).

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES

So we can see from the above source code that the automatic release pool is actually a doubly linked list composed of, and the AutoreleasePoolPagesums in its structure point to its predecessor and successor respectively.childparent

Insert image description here

The individual AutoreleasePoolPagestructure is as follows:
Insert image description here

56 bits are used to store AutoreleasePoolPagemember variables, and the remaining 0x100816038 ~ 0x100817000 are used to store objects added to the automatic release pool.

  • The first member variable of this structure is magic, which we isaalso learned in , isawhich determines whether the object has not been initialized. The same is true here, used to check whether the node has been initialized.
  • begin()And end()the instance methods of these two classes help us quickly obtain 0x100816038 ~ 0x100817000the boundary address of this range.
  • nextPoints to the next empty memory address. If one is added to the address pointed to by next object, it will move to the next empty memory address as shown in the figure below, just like the top pointer on the stack.
  • threadSaves the thread where the current page is located.
  • depthThe depth represented pageis 0 for the first time, and pagethe size of each is 4096 bytes (0x1000 in hexadecimal). Each time one is initialized page, depthit is incremented by one.
  • POOL_SENTINELIt's the sentinel object, it's just nilan alias for separation Autoreleasepool. POOL_BOUNDARYThe literal translation is POOLthe border of . Its function is to separate pagethe objects. Because not every object stored between and occupies exactly one block push, it may be full or exceeded, so this helps us separate the objects between each block. In other words, this object may store many blocks, and is used to separate the objects of each block.poppagePOOL_BOUNDARY@autoreleasepoolpage@autoreleasepoolPOOL_BOUNDARY@autoreleasepool
#define POOL_SENTINEL nil

If you add an object to the one just initialized above page, it will be added at the point pointed by next, and next will be moved back one position.
Insert image description here

And during each initialization call of the automatic release pool objc_autoreleasePoolPush, one will be pushed POOL_SENTINEL pushto the top of the stack of the automatic release pool and the POOL_SENTINELsentinel object will be returned.

int main(int argc, const char * argv[]) {
    
    
    {
    
    
    	//这里的 atautoreleasepoolobj 就是一个 POOL_SENTINEL
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();
        
        // do whatever you want
        
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

The one above atautoreleasepoolobjis one POOL_SENTINEL.
When the method objc_autoreleasePoolPopis called, a message will be sent to the objects in the automatic release pool releaseuntil the first one POOL_SENTINEL.

Insert image description here

This is also autoreleasepoolthe reason why the objects can be released accurately: at this autoreleasepool pushtime, the address of a sentinel object ( ) will be returned POOL_SENTINELand given to it pop. Then you popwill know that when the poprelease is executed, it will be released to the sentinel object ( POOL_SENTINEL) and it will stop. , and the content released is exactly the object in the automatic release pool.

1.objc_autoreleasePoolPush method:

void *objc_autoreleasePoolPush(void) {
    
    
    return AutoreleasePoolPage::push();
}

The method is called here AutoreleasePoolPage::push():

static inline void *push() 
{
    
    
    id *dest;
    // POOL_BOUNDARY就是nil
    // 首先将一个哨兵对象插入到栈顶
    if (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;
}

autoreleaseFastThe method called hotPagerefers to the one currently being used AutoreleasePoolPage:

static inline id *autoreleaseFast(id obj)
{
    
    
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {
    
    //有 hotPage 并且当前 page 不满,将object加入当前栈中
       return page->add(obj);
   } else if (page) {
    
    //有hotPage 但当前page已满,找未满页或创建新页,将object添加到新页中
       return autoreleaseFullPage(obj, page);
   } else {
    
    //无hotPage,创建hotPage,加入其中
       return autoreleaseNoPage(obj);
   }
}

1.1 If there is hotPage and the current page is not satisfied, directly call page->add(obj) to add the object to the automatic release pool.

// 这其实就是一个压栈操作,将对象加入AutoreleasePoolPage,然后移动栈顶指针
id *add(id obj) {
    
    
    id *ret = next;
    *next = obj;
    next++;
    return ret;
}

1.2 There is a hotPage but the current page is full. Find the page that is not full or create a new page and add the object to the new page. autoreleaseFullPage (called when the current page is full):

static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    
    
//一直遍历,直到找到一个未满的 AutoreleasePoolPage,如果找到最后还没找到,就新建一个 AutoreleasePoolPage
    do {
    
    
        if (page->child) 
        	page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());
	
	//将找到的,或者构建的page作为hotPage,然后将obj加入
    setHotPage(page);
    return page->add(obj);
}

1.3 If there is no hotPage, create a hotPage and join it:
At this time, since there is no hotPage in the memory AutoreleasePoolPage, the doubly linked list of the automatic release pool must be built from scratch. Then the current page table, as the first page table, has no parentpointers. And when we create it for the first time, pageit needs to be POOL_SENTINELmarked at the beginning, so that we can pageknow where it ends.

static id *autoreleaseNoPage(id obj) {
    
    
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); // 创建AutoreleasePoolPage
    setHotPage(page); // 设置page为当前页
 
    if (obj != POOL_SENTINEL) {
    
     // 加POOL_SENTINEL哨兵
        page->add(POOL_SENTINEL);
    }
 
    return page->add(obj); // 将obj加入
}

autorelease method

Let's think about it now dest = autoreleaseFast(POOL_BOUNDARY); the operation passes POOL_BOUNDARYvariables, not objects, so how is it stored in?

Through debugging and looking at the assembly, we found that it actually called objc_retainAutoreleasea method. After calling layer by layer, we found that it called autoreleasea method:

static inline id autorelease(id obj)
{
    
    
    printf("static inline id autorelease%p\n", obj);
    assert(obj);
    assert(!obj->isTaggedPointer());
    id *dest __unused = autoreleaseFast(obj);
    assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}

2.objc_autoreleasePoolPop method:

void objc_autoreleasePoolPop(void *ctxt) {
    
    
    AutoreleasePoolPage::pop(ctxt);
}

We usually pass in a sentinel object in this method POOL_SENTINELto facilitate release, as shown in the figure below to release the object:
Insert image description here

The calling popmethod is as follows:

static inline void pop(void *token) {
    
    
    AutoreleasePoolPage *page = pageForPointer(token);//使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage
    id *stop = (id *)token;

    page->releaseUntil(stop);//调用 releaseUntil 方法释放栈中的对象,直到 stop 位置,stop就是传递的参数,一般为哨兵对象

	//调用 child 的 kill 方法,系统根据当前页的不同状态kill掉不同child的页面
	//releaseUntil把page里的对象进行了释放,但是page本身也会占据很多空间,所以要通过kill()来处理,释放空间
    if (page->child) {
    
    
        if (page->lessThanHalfFull()) {
    
     // 当前page小于一半满
            page->child->kill(); // 把当前页的孩子杀掉
        } else if (page->child->child) {
    
     // 否则,留下一个孩子,从孙子开始杀
            page->child->child->kill();
        }
    }
}

Apple assumes that pagehalf of it is not full, which means that the remaining space is enough for the time being. You can delete pageall the excess to free up the space. If it is more than half, it thinks that the next page is still necessary. Maybe the added objects are too large. The more you can use, so if you lose a grandson , having a son is enough for the time being.child pagekillpagekillpagepage

token

  • okenis a pointer to poolthePOOL_BOUNDARY
  • tokenThe essence is a pointer to the sentinel object, which stores the address pushinserted each timePOOL_BOUNDARY
  • Only when you push for the first time will pagea POOL_BOUNDARY[either pageit is full, or there is no hotPageneed to use a new pageone] be inserted into it. It does not pagealways start withPOOL_BOUNDARY

2.1 pageForPointer gets AutoreleasePoolPage:

pageForPointerThe method mainly obtains the first address of the page where the current pointer is located through the operation of memory addresses:

static AutoreleasePoolPage *pageForPointer(const void *p) {
    
    
    return pageForPointer((uintptr_t)p);
}

static AutoreleasePoolPage *pageForPointer(uintptr_t p) {
    
    
    AutoreleasePoolPage *result;
    uintptr_t offset = p % SIZE;

    assert(offset >= sizeof(AutoreleasePoolPage));

    result = (AutoreleasePoolPage *)(p - offset);
    result->fastcheck(); // 检查当前的result是不是一个AutoreleasePoolPage

    return result;
}

Modulo the pointer and the size of the page, which is 4096, to get the offset of the current pointer, because everything AutoreleasePoolPageis aligned in memory:

p = 0x100816048
p % SIZE = 0x48
result = 0x100816000

The last method called fastCheck()is used to check whether the current result is one AutoreleasePoolPage.

By checking whether a member in the magic_t structure is 0xA1A1A1A1.

2.2 releaseUntil releases the object:

releaseUntilThe method is implemented as follows:

void releaseUntil(id *stop) {
    
    
    while (this->next != stop) {
    
     // 不等于stop就继续pop
        AutoreleasePoolPage *page = hotPage(); // 获取当前页

        while (page->empty()) {
    
     // 当前页为空,就找其父页,并将其设置为当前页
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 将内存内容标记为SCRIBBLE
        page->protect();

        if (obj != POOL_SENTINEL) {
    
     // 该对象不为标识POOL_SENTINEL,就释放对象
            objc_release(obj);
        }
    }

    setHotPage(this);
}

Its implementation is still very easy, using a whileloop to continuously release AutoreleasePoolPagethe content until it nextis pointed to stop.

Use memsetto set the contents of memory to SCRIBBLEand then use to objc_releaserelease the object.

2.3 kill() method:
At this point, the only method without analysis is left kill, and it will delete all the current page and sub-pages:

void kill() {
    
    
    AutoreleasePoolPage *page = this; // 获取当前页
    while (page->child) page = page->child; // child存在就一直往下找,直到找到一个不存在的

    AutoreleasePoolPage *deathptr;
    do {
    
    
        deathptr = page;
        page = page->parent;
        if (page) {
    
    
            page->unprotect();
            page->child = nil; // 将其child指向置nil,防止出现悬垂指针
            page->protect();
        }
        delete deathptr; // 删除
    } while (deathptr != this); // 直到this处停止
}

3.autorelease method

We already have a better understanding of the life cycle of the automatic release pool. The last topic we need to understand is the autoreleaseimplementation of the method. Let's first take a look at the call stack of the method:

- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)

In autoreleasethe method call stack, the method mentioned above will eventually be called autoreleaseFastand the current object will be added AutoreleasePoolPageto it.
The implementation of these methods in this section is very easy. It only requires some parameter checks and finally calls the autoreleaseFast method:

inline id objc_object::rootAutorelease() {
    
    
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    
    
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
    
    
   id *dest __unused = autoreleaseFast(obj);
   return obj;
}

3. Answers to the opening questions

Do you understand the problem mentioned at the beginning? When a running loop ends, that is, before the object autoreleasepoolis released autorelease, autoreleasepoolthe memory keeps increasing, and the APP will experience memory peaks, freezes, and may even be forced to close by the system and cause a crash.

Therefore, by @autoreleasepoolensuring that the objects generated in each cycle autoreleaseare released in time, we can avoid the above problems:

for (int i = 0; i < 1000000; i++) {
    
    
   @autoreleasepool {
    
    
          NSNumber *num = [NSNumber numberWithInt:i];
          NSLog(@"%@", num);
   }
}

There are also @autoreleasepooluses such as delayed release, allowing objects to exist beyond the function scope.

4. Summary

In general, autoreleasepoolit is a doubly linked list. Each node in the linked list is a stack. The pointer pointed to is saved in the stack autoreleasepooland objects that need to be automatically released by the pool are added to it, so autoreleasepoolthe reference count of all objects in it will be +1. , once out autoreleasepool, there is no pointer pointing to the object, the reference count of the object will be -1, and under ARC, xcodeit will be automatically added for the code autoreleasepool.

  • The automatic release pool is AutoreleasePoolPageimplemented in the form of a doubly linked list
  • When an object calls autoreleasea method, the object is added AutoreleasePoolPageto the stack
  • Calling a method will send a message AutoreleasePoolPage::popto the object on the stackrelease

About sentinel objects (POOL_BOUNDARY) and next pointers:

There is only one next pointer, which always points to the next address where the autoreleasepool can be stored. There can be many sentinel objects. Each autoreleasePool corresponds to a sentinel object, indicating where the autoreleasePool object starts to be stored.

next and child:
next points to the next address that can store the object object, and child is the parameter of autoreleasePoolPage, pointing to the next page.

The relationship between autoreleasePoolPage and RunLoop:

Insert image description here
RunLoop and AutoReleasePool correspond one to one through threads
. When the Autorelease pool is not added manually, the Autorelease object is released before the current runloop enters sleep waiting
. When a runloop is working in a non-stop loop, then the runloop will definitely After BeforeWaiting (preparing to enter sleep): When going to BeforeWaiting (preparing to enter sleep), _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() will be called to release the old pool and create a new pool. Then these two methods will destroy the object to be released, so We don't need to worry about Autorelease's memory management issues at all.

Timing of RunLoop creating and releasing the automatic release pool:

When entering RunLoop, create an AutoReleasePool.
When preparing to sleep, release the old AutoReleasePool and create a new AutoReleasePool.
When RunLoop exits, release AutoReleasePool.

Is there only one doubly linked list to save autoreleasePoolPage? That is, are the autoreleasePoolPage of all threads stored in a linked list, or does each thread save its own linked list? And where is the head of the linked list, which is the entry position of the linked list, stored? Who will control it?

A thread has its own separate autoreleasePool linked list, or it may not have a linked list. The hotPage of the linked list is stored in TLS. Because the linked list is bidirectional, the header and tail can be found through hotpage, and there is no need to store the header separately.

When you need to manually add autoreleasepool yourself

  • Write programs that are not based on UI frameworks, such as command line tools;
  • Create a large number of temporary objects in a loop;
  • Use child threads created by non-Cocoa programs;

Guess you like

Origin blog.csdn.net/m0_63852285/article/details/132168855
ios