• Writing in the front
  • Start with the main function
  • @autoreleasepool
  • What is AutoreleasePool
  • summary
  • The resources

Keep an eye on the repository for updates: ios-source-code-analyze

Since memory management in Objective-C is a big topic, I will break down some of the mechanisms of memory management into two articles, one of which will examine automatic release pooling and autorelease methods. Another section looks at the implementation of the retain, release methods, and automatic reference counting.

Writing in the front

This article introduces objective-C auto-release pooling at the source level, as well as the implementation of autoreleases for methods.

Start with the main function

mainFunctions are one of the most obscure functions in iOS development, well hidden inSupporting FilesFolder, but is the entrance to the entire iOS app.

The contents of the main.m file look like this:

int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

The @AutoReleasepool block contains only one line of code that hands all events and messages to UIApplication for processing, but that is not the focus of this article.

Note that the entire iOS app is contained in an auto-release pool block.

@autoreleasepool

What exactly is @autoreleasepool? We use clang-rewrite-objc main.m on the command line to get the compiler to rewrite the file:

$ clang -rewrite-objc main.m
Copy the code

After a bunch of warnings were generated, a main.cpp file was added to the current directory

This removes the other useless code in the main function.

In this file, there is a very strange __AtAutoreleasePool structure. The previous comment reads /* @autoreleasepopl */. That is, @autoreleasepool {} is converted to an __AtAutoreleasePool structure:

{
    __AtAutoreleasePool __autoreleasepool;
}
Copy the code

To make sense of this line of code, look for a structure named __AtAutoreleasePool in main. CPP:

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

This structure calls the objc_autoreleasePoolPush() method at initialization and the objc_autoreleasePoolPop method at destructor.

This shows that our main function actually works like this:

int main(int argc, const char * argv[]) {
    {
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();

        // do whatever you want

        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}
Copy the code

Autoreleasepool just helps us to cut out these two lines of code, make it look better, and then analyze the auto-release pool implementation based on the above two methods.

What is AutoreleasePool

This section begins by examining the implementation of objc_autoreleasePoolPush and objc_autoreleasePoolPop methods:

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

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

The above method appears to encapsulate the static methods push and POP corresponding to AutoreleasePoolPage.

This section steps through the code in the following order:

  • The structure of AutoreleasePoolPage
  • Objc_autoreleasePoolPush method
  • Objc_autoreleasePoolPop method

The structure of AutoreleasePoolPage

AutoreleasePoolPage is a C++ class:

Its definition in nsobject.mm looks like this:

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};
Copy the code
  • magicUsed for the currentAutoreleasePoolPage integrityThe check
  • threadSave the thread where the previous page is located

Each AutoreleasePoolPage consists of a series of AutoreleasepoolPages, each of which is 4096 bytes (hexadecimal 0x1000).

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES
Copy the code

Two-way linked list

Autoreleasepoolpages in the auto release pool are connected in a bidirectional linked list:

Parent and child are the Pointers used to construct a bidirectional list.

Automatically release the stack in the pool

If one of our AutoreleasepoolPages is initialized in 0x100816000 ~ 0x100817000, its structure in memory is as follows:

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

The instance methods of the begin() and end() classes help us quickly get the boundary addresses in the 0x100816038 to 0x100817000 range.

Next refers to the next empty memory address. If an object is added to the address to which next refers, it will be moved to the next empty memory address as shown below:

Hiwat and depth are not covered in this article because they do not affect the overall auto-release pool implementation and are not on the call stack for key methods.

POOL_SENTINEL (Sentinel object)

At this point, you might be wondering what POOL_SENTINEL is and why it’s on the stack.

To answer the first question first: POOL_SENTINEL is just an alias for nil.

#define POOL_SENTINEL nil
Copy the code

Each autorelease pool initialization call to objc_autoreleasePoolPush pushes a POOL_SENTINEL object to the top of the autorelease pool stack and returns the POOL_SENTINEL object.

int main(int argc, const char * argv[]) {
    {
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();

        // do whatever you want

        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}
Copy the code

The atAutoReleasepoolobj above is a POOL_SENTINEL.

When the method objc_autoreleasePoolPop is called, a release message is sent to the objects in the auto-release pool up to the first POOL_SENTINEL:

Objc_autoreleasePoolPush method

With POOL_SENTINEL in mind, let’s revisit the objc_autoreleasePoolPush method:

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

It calls the AutoreleasePoolPage class method push, which is also very simple:

static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}
Copy the code

A key method, autoreleaseFast, is entered here, passing in the POOL_SENTINEL object:

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);
   }
}
Copy the code

There are three ways to select different code for execution:

  • There arehotPageAnd the currentpagediscontent
    • callpage->add(obj)Method to add an object toAutoreleasePoolPageThe stack
  • There arehotPageAnd the currentpageIs full
    • callautoreleaseFullPageInitialize a new page
    • callpage->add(obj)Method to add an object toAutoreleasePoolPageThe stack
  • There is nohotPage
    • callautoreleaseNoPageTo create ahotPage
    • callpage->add(obj)Method to add an object toAutoreleasePoolPageThe stack

The last one will call Page -> Add (obj) to add the object to the automatic release pool.

HotPage is the AutoreleasePoolPage that is currently in use.

Page ->add Adds an object

Id *add(id obj) adds the object to the auto release pool page:

id *add(id obj) {
    id *ret = next;
    *next = obj;
    next++;
    return ret;
}
Copy the code

The author has dealt with this method, which is more convenient to understand.

This method is simply a stack operation, adding an object to the AutoreleasePoolPage and moving the pointer to the top of the stack.

AutoreleaseFullPage (current hotPage full)

AutoreleaseFullPage is called when the current hotPage is full:

static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    setHotPage(page);
    return page->add(obj);
}
Copy the code

It iterates through the bidirectional list from the incoming page until:

  1. I found a full oneAutoreleasePoolPage
  2. Pass in using the constructorparentCreate a new oneAutoreleasePoolPage

Once an AutoreleasePoolPage is found that can be used, the page is marked as hotPage, and the page-> Add method analyzed above is invoked to add the object.

AutoreleaseNoPage (no hotPage)

If hotPage does not currently exist in memory, the autoreleaseNoPage method is called to initialize an AutoreleasePoolPage:

static id *autoreleaseNoPage(id obj) { AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); if (obj ! = POOL_SENTINEL) { page->add(POOL_SENTINEL); } return page->add(obj); }Copy the code

Since AutoreleasePoolPage does not currently exist in memory, the new AutoreleasePoolPage will have no parent pointer.

After initialization, the current page is marked as hotPage, and a POOL_SENTINEL object is first added to the page to ensure that no exceptions are raised when pop is called.

Finally, add obJ to the automatic release pool.

Objc_autoreleasePoolPop method

Also, review the objc_autoreleasePoolPop method mentioned above:

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

It seems that passing any pointer is ok, but there are no examples of passing other objects throughout the project. However, it is possible to pass in other Pointers to this method, releasing the auto-release pool to the appropriate location.

We normally pass in a sentinel object called POOL_SENTINEL and release the object as shown below:

Test the objc_autoreleasePoolPop behavior

Before continuing with this method, do a little test to see how it behaves by passing in a non-sentry object to objc_autoreleasePoolPop.

Here is the source code in the main.m file:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        NSString *s = @"Draveness";
        [s stringByAppendingString:@"-Suffix"];

    }
    return 0;
}
Copy the code

Make a breakpoint on this line of code because the autoRelease method is called to add the string to the auto-release pool:

When the code runs here, the LLDB prints out the stack contents in the current hotPage:

  1. throughstaticMethod to get the currenthotPage
  2. printAutoreleasePoolPageThe contents of the
  3. Print the currentnextWhat the pointer points to, and what came before,2 -The time has comebegin()location
  4. useprint() andprintAll() Prints the contents of the auto-release pool

We then pass a pointer to the string @” draPS-suffix “to the POP method to test whether the POP method can pass non-sentry parameters.

When you print the contents of the current AutoreleasePoolPage again, the string is gone, indicating that it is possible to pass a non-sentry parameter to the POP method, although we don’t normally pass a non-sentry object.


Let’s go back to the analysis of the objc_autoreleasePoolPop method, which calls the AutoreleasePoolPage:: POP method:

static inline void pop(void *token) { AutoreleasePoolPage *page = pageForPointer(token); id *stop = (id *)token; page->releaseUntil(stop); if (page->child) { if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); }}}Copy the code

A lot of extraneous code has been removed and formatting tweaked in this method.

This static method does three things altogether:

  1. usepageForPointerGets the currenttokenWhere theAutoreleasePoolPage
  2. callreleaseUntilMethods to releaseIn the stackObject untilstop
  3. callchildkillmethods

I’m still not sure why I want to kill different child pages based on the current page state.

if (page->lessThanHalfFull()) {
    page->child->kill();
} else if (page->child->child) {
    page->child->child->kill();
}
Copy the code

PageForPointer get AutoreleasePoolPage

The pageForPointer method returns the first address of the page where the pointer is located by manipulating the memory address:

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();

    return result;
}
Copy the code

Modulo the pointer to the page size (4096) to get the offset of the current pointer, since all autoreleasepoolPages are aligned in memory:

p = 0x100816048
p % SIZE = 0x48
result = 0x100816000
Copy the code

The last method called, fastCheck(), checks if the current result is an AutoreleasePoolPage.

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

ReleaseUntil Releases the object

The releaseUntil method is implemented as follows:

void releaseUntil(id *stop) { while (this->next ! = stop) { 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)); page->protect(); if (obj ! = POOL_SENTINEL) { objc_release(obj); } } setHotPage(this); }Copy the code

It’s easy to implement, with a while loop that continues releasing AutoreleasePoolPage until next points to Stop.

Use memset to SCRIBBLE the contents of the memory, and then use objc_release to free the object.

The kill () method

At this point, the only method left without analysis is kill, which deletes the current page and all its children:

void kill() { AutoreleasePoolPage *page = this; while (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; page = page->parent; if (page) { page->unprotect(); page->child = nil; page->protect(); } delete deathptr; } while (deathptr ! = this); }Copy the code

Autorelease method

Now that we have a good understanding of the auto-release pool lifecycle, the last topic to look at is the implementation of the autoRelease method. Let’s look at the call stack for the method:

- [NSObject autorelease] └── id () └─ id () ├ ─ 088 (2 Obj AutoreleasePoolPage: : autorelease (id) └ ─ ─ the static id AutoreleasePoolPage: : autoreleaseFast (id obj) ├ ─ ─ id * add (id obj) ├── Static Id *autoreleaseFullPage(Id obj, ├─ ├─ ├─ id *add(id obj) ├─ static ID ├── ├─ ├─ ├─ ├─ ├─ Id * Add (Id obj)Copy the code

In the call stack of the AutoRelease method, the autoreleaseFast method mentioned above is eventually called to add the current object to the AutoreleasePoolPage.

The methods in this section are easy to implement, with a few parameter checks and a final call to 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; }Copy the code

Since you’ve analyzed the implementation of the autoreleaseFast method above, I won’t say more here.

summary

Now that the entire AutoreleasePool implementation and the AutoRelease method have been analyzed, let’s review some of the content of the article:

  • Automatic release pool is made byAutoreleasePoolPageIt is implemented in a two-way linked list
  • When the object callsautoreleaseMethod, the object is addedAutoreleasePoolPageThe stack
  • callAutoreleasePoolPage::popMethods are sent to objects on the stackreleaseThe message

The resources

  • What is autoreleasepool? – Objective-C
  • Using Autorelease Pool Blocks
  • NSAutoreleasePool
  • Autorelease behind the black screen

Keep an eye on the repository for updates: ios-source-code-analyze

About pictures and reprints





Creative Commons Attribution 4.0 International License agreement

About comments and comments

Autoreleasepool —- In-depth analysis of autoreleasepool

Autoreleasepool —- In-depth analysis autoreleasepool · Faith-oriented programming

Follow: Draveness dead simple