• Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

1. Automatic release pool

1.1 Introduction to automatic discharge pool

AutoreleasePool is an automatic memory reclamation mechanism in OC that delays the addition of the variable release in AutoreleasePool. That is, when we create an object and add it to the AutoreleasePool, it is not immediately released. Will wait until a runloop ends or autoreleasepool{} is out of scope before being released

1.2 Underlying Mechanism of Automatic Release Pool

To explore the underlying structure of auto-release pools, use Clang or XCRun to convert code to CPP files. After converting, you can see that @autoreleasepool{} becomes {__AtAutoreleasePool __autoreleasepool; }

If __AtAutoreleasePool is searched in the generated CPP file and found to be a structure, then @autoreleasepool{} calls the __AtAutoreleasePool constructor and destructor: Objc_autoreleasePoolPush () and objc_autoreleasePoolPop (atautoreleasepoolobj);

After the breakpoint, objc_autoreleasePoolPush is found in objC source code, so let’s explore the source code.

objc_autoreleasePoolPush

Here you can see objc_autoreleasePoolPush and invoked the objc_autoreleasePoolPop AutoreleasePoolPage: : push (); And AutoreleasePoolPage: : pop (CTXT); . So what is AutoreleasePoolPage here?

AutoreleasePoolPage is inherited from AutoreleasePoolPageData, and as you can see from the comments, the AutoreleasePoolPageData is a stack structure that stores Pointers. Each pointer points to the object to be released or POOL_BOUNDARY, which is the automatic release pool boundary. The auto-release pool is a class that pushes objects continuously, meaning that they are pushed in and out of the stack, but not without limit. If you keep pushing it out of the stack, the pointer will move and kill, and if there is no boundary, it will destroy someone else’s memory, and then it will cause a wild pointer problem if you access the broken object later. The automatic release pool is guaranteed to be unimportant — a linkes list of pages.

Looking at AutoreleasePoolPageData, see some of the properties and constructors here.

  • magic: verifies the integrity of the AutoreleasePoolPage structure;
  • next: points to the next location of the newly added Autoreleased object when initialized

begin() ;

  • thread: points to the current thread;
  • parent: refers to the parent node, the first node’s parent value is nil;
  • child: points to a child, and the last child is nil;
  • depth: represents the depth, starting from 0 and increasing by 1.
  • hiwat: indicates the high water mark

Set automatic Reference counting in Build Settings to No.

Create an NSObject object and call autoRelease, introduce and call the _objc_autoreleasePoolPrint method and run it.

Run to get the following print. The objects here are a sentinel object and an NSObject object that you add for yourself.

Now look at objc_autoreleasePoolPush. This is going to go inside the autoreleaseFast.

When you see autoreleaseFast, this is where you decide.

  • Call if the page exists and is not fullpage->addAdd this object to the page,
  • Call if page exists but page is fullautoreleaseFullPage.
  • Called if page does not existautoreleaseNoPage.

So the first time you do it, autoreleaseNoPage is called.

autoreleaseNoPage

See autoreleaseNoPage. Here you create the AutoreleasePoolPage, set the current page to HotPage, add the sentinel object, and finally add the object to be added.

When you see the constructor for AutoreleasePoolPage, the constructor for AutoreleasePoolPageData is called to initialize the property. If the parent exists, set the child of the parent to itself. Here you can see that nil is passed out, so parent doesn’t exist.

There is also a call to begin, which runs after a breakpoint is set in begin.

See here that this is AutoreleasePoolPage and has a size of 56.

When you look at the structure of AutoreleasePoolPage, you see that the size is indeed 56.

Static does not occupy the global heap. Here one uint32_t is 4 bytes, 4 uint32_t is 16 bytes.

That is, the insertion starts below the member variable. The structure is as follows:

Here 56 is the size of the structure, followed by 0x10480A038 is the sentinel object, followed by the self-added object to be destroyed.

AutoreleasePoolPageData also calls objc_thread_self, where tls_get_direct is called to get the current thread.

autoreleaseFullPage

Next you see the autoreleaseFullPage that is called when the autoreleasePage is full. This recursively finds the last child page, creates a new page, sets the new page to HotPage, and finally adds the release object to add.

This is a pagination structure because you’re constantly going on and off the stack, and you’re doing a lot of memory manipulation, and if you only have one page, and all the objects are in that one page, then you’re doing a lot of work and you’re not managing it. And if something goes wrong locally, it can affect the entire page. Paging, on the other hand, affects only partial pages. Also, pages need not be contiguous in memory.

So when will this place be full? So here we loop for 504 times, and we see that the page has a tag full.

When I set this to 505, I noticed pagination. And you can see on the second page there are no sentinel objects. So here you can see, actually one page can store 505 objects, but because of the first page of the sentinel object, so only 504 objects. So the page size is 505 * 8 + 56 = 4096, which is 4K.

page->add

The main thing we’re doing here is storing objC by memory translation.

objc_autoreleasePoolPop

It checks if hotPage exists, moves the page, adjusts it, and then calls popPage to remove it.

PopPage will get the parent page, delete the current page and set the parent page to HotPage

Page ->kill will delete page.

1.3 Can the Automatic Release Pool be nested

The nested autorelease pool is added to the outer autorelease pool and is released when the scope ends.

1.4 Conditions for automatically releasing a Pool

MRC cases

As you can see, in MRC cases, objects are not added to the automatic release pool if they have not called autoRelease.

The ARC case

As you can see, in ARC cases, objects that call alloc are not added to the auto-release pool. In ARC, alloc, new, copy, and mutablecopy are not added to the auto release pool.