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 main
the code of a function, we will find that there is this thing in it @autoreleasepool{}
. After using clang
compilation: @autoreleasepool{...}
it is compiled into {__AtAutoreleasePool __autoreleasepool; ... }
.
__AtAutoreleasePool
What 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 main
seems 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 ARC
write in the environment @autoreleasepool{}
is actually a simple use of automatic release pool creation and release.
It will {
create an auto-release pool at , destroy the auto-release pool at } and issue release
a notification, allowing the variables in it to release
operate on their own.
二、AutoreleasePoolPage
From above __AtAutoreleasePool
we can see the objc_autoreleasePoolPush
sum 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 AutoreleasePoolPage
composed of a series, and AutoreleasePoolPage
the 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 AutoreleasePoolPage
sums in its structure point to its predecessor and successor respectively.child
parent
The individual AutoreleasePoolPage
structure is as follows:
56 bits are used to store AutoreleasePoolPage
member 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 weisa
also learned in ,isa
which determines whether the object has not been initialized. The same is true here, used to check whether the node has been initialized. begin()
Andend()
the instance methods of these two classes help us quickly obtain0x100816038 ~ 0x100817000
the boundary address of this range.next
Points to the next empty memory address. If one is added to the address pointed to by nextobject
, it will move to the next empty memory address as shown in the figure below, just like the top pointer on the stack.thread
Saves the thread where the current page is located.depth
The depth representedpage
is 0 for the first time, andpage
the size of each is 4096 bytes (0x1000 in hexadecimal). Each time one is initializedpage
,depth
it is incremented by one.POOL_SENTINEL
It's the sentinel object, it's justnil
an alias for separationAutoreleasepool
.POOL_BOUNDARY
The literal translation isPOOL
the border of . Its function is to separatepage
the objects. Because not every object stored between and occupies exactly one blockpush
, 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.pop
page
POOL_BOUNDARY
@autoreleasepool
page
@autoreleasepool
POOL_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.
And during each initialization call of the automatic release pool objc_autoreleasePoolPush
, one will be pushed POOL_SENTINEL push
to the top of the stack of the automatic release pool and the POOL_SENTINEL
sentinel 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 atautoreleasepoolobj
is one POOL_SENTINEL
.
When the method objc_autoreleasePoolPop
is called, a message will be sent to the objects in the automatic release pool release
until the first one POOL_SENTINEL
.
This is also autoreleasepool
the reason why the objects can be released accurately: at this autoreleasepool push
time, the address of a sentinel object ( ) will be returned POOL_SENTINEL
and given to it pop
. Then you pop
will know that when the pop
release 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;
}
autoreleaseFast
The method called hotPage
refers 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 parent
pointers. And when we create it for the first time, page
it needs to be POOL_SENTINEL
marked at the beginning, so that we can page
know 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_BOUNDARY
variables, not objects, so how is it stored in?
Through debugging and looking at the assembly, we found that it actually called objc_retainAutorelease
a method. After calling layer by layer, we found that it called autorelease
a 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_SENTINEL
to facilitate release, as shown in the figure below to release the object:
The calling pop
method 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 page
half of it is not full, which means that the remaining space is enough for the time being. You can delete page
all 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 page
kill
page
kill
page
page
token
oken
is a pointer topool
thePOOL_BOUNDARY
token
The essence is a pointer to the sentinel object, which stores the addresspush
inserted each timePOOL_BOUNDARY
- Only when you push for the first time will
page
aPOOL_BOUNDARY
[eitherpage
it is full, or there is nohotPage
need to use a newpage
one] be inserted into it. It does notpage
always start withPOOL_BOUNDARY
2.1 pageForPointer gets AutoreleasePoolPage:
pageForPointer
The 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 AutoreleasePoolPage
is 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:
releaseUntil
The 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 while
loop to continuously release AutoreleasePoolPage
the content until it next
is pointed to stop
.
Use memset
to set the contents of memory to SCRIBBLE
and then use to objc_release
release 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 autorelease
implementation 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 autorelease
the method call stack, the method mentioned above will eventually be called autoreleaseFast
and the current object will be added AutoreleasePoolPage
to 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 autoreleasepool
is released autorelease
, autoreleasepool
the 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 @autoreleasepool
ensuring that the objects generated in each cycle autorelease
are 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 @autoreleasepool
uses such as delayed release, allowing objects to exist beyond the function scope.
4. Summary
In general, autoreleasepool
it is a doubly linked list. Each node in the linked list is a stack. The pointer pointed to is saved in the stack autoreleasepool
and objects that need to be automatically released by the pool are added to it, so autoreleasepool
the 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, xcode
it will be automatically added for the code autoreleasepool
.
- The automatic release pool is
AutoreleasePoolPage
implemented in the form of a doubly linked list - When an object calls
autorelease
a method, the object is addedAutoreleasePoolPage
to the stack - Calling a method will send a message
AutoreleasePoolPage::pop
to 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:
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;