Write in front: iOS underlying principle exploration is my usual development and learning in the accumulation of a section of advanced road. Record my continuous exploration of the journey, I hope to be helpful to all readers.Copy the code

The directory is as follows:

  1. IOS underlying principles of alloc exploration
  2. The underlying principles of iOS are explored
  3. The underlying principles of iOS explore the nature of objects & isa’s underlying implementation
  4. Isa-basic Principles of iOS (Part 1)
  5. Isa-basic Principles of iOS (Middle)
  6. Isa-class Basic Principles of iOS Exploration (2)
  7. IOS fundamentals explore the nature of Runtime Runtime & methods
  8. Objc_msgSend: Exploring the underlying principles of iOS
  9. Slow lookups in iOS Runtime
  10. A dynamic approach to iOS fundamentals
  11. The underlying principles of iOS explore the message forwarding process
  12. Dyld (part 1)
  13. IOS Basic Principles of application loading principle dyld (ii)
  14. IOS basic principles explore the loading of classes
  15. The underlying principles of iOS explore the loading of categories
  16. IOS underlying principles to explore the associated object
  17. IOS underlying principle of the wizard KVC exploration
  18. Exploring the underlying principles of iOS: KVO Principles | More challenges in August
  19. Exploring the underlying principles of iOS: Rewritten KVO | More challenges in August
  20. The underlying principles of iOS: Multi-threading | More challenges in August
  21. GCD functions and queues in iOS
  22. GCD principles of iOS (Part 1)
  23. IOS Low-level – What do you know about deadlocks?
  24. IOS Low-level – Singleton destruction is possible?
  25. IOS Low-level – Dispatch Source
  26. IOS bottom – a fence letter blocks the number
  27. IOS low-level – Be there or be Square semaphore
  28. IOS underlying GCD – In and out into a scheduling group
  29. Basic principles of iOS – Basic use of locks
  30. IOS underlying – @synchronized Flow analysis
  31. IOS low-level – The principle of lock exploration
  32. IOS Low-level – allows you to implement a read/write lock
  33. Implementation of Objective-C Block
  34. Implementation of Objective-C Block
  35. IOS bottom – Block, comprehensive resolution!
  36. IOS Basics – Startup Optimization (part 1)
  37. IOS Basics – Startup Optimization (2)
  38. Exploration of basic principles of iOS — Memory management of memory five areas
  39. Tagged Pointer Format Changes for memory management
  40. Retain & Release for iOS
  41. Exploring the underlying principles of iOS — Weak reference tables for memory management

Summary of the above column

  • Summary of iOS underlying principles of exploration

Sort out the details

  • Summary of iOS development details

preface

Today in the memory management series we continue our exploration of the underlying principles of @Autoreleasepool. So without further ado, let’s get started.

Automatic release tank

The most common place for the autoreleasepool @autoreleasepool is in our project’s Main. Today we are going to take a deeper look at the underlying structure and implementation.

To understand the underlying structure, let’s take a look at xcRun and see what it looks like when compiled:

As you can see, @autoreleasepool is commented out and replaced with __AtAutoreleasePool __autoreleasepool;

__AtAutoreleasePool is a structure

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

Compiled __AtAutoreleasePool __autoreleasepool; This line of code is equivalent to calling the constructor at the beginning of the scope and the destructor at the end of the scope.

That is, call:

  • objc_autoreleasePoolPush();
  • objc_autoreleasePoolPop(atautoreleasepoolobj);

Using the symbol breakpoint we can locate its source:

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

AutoreleasePoolPage

/*********************************************************************** Autorelease pool implementation The auto-release pool for threads is a stack of Pointers. Each pointer is either an object to be released, or POOL_BOUNDARY automatically releases the pool boundary. The boundary object is used to mark the boundary of the pool, preventing operations from exceeding the boundary when exiting the stack. The pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is ejected, each object hotter than the sentinel is released. The stack is divided into a bidirectional linked list. Pages are added and removed as necessary. Thread local storage points to the newly automatically freed hot page object store. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj));
BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token));

class AutoreleasePoolPage : private AutoreleasePoolPageData
{... }Copy the code

AutoreleasePoolPageData

struct AutoreleasePoolPageData
{

...
    // Check AutoreleasePoolPage structure is complete;
    magic_t const magic; / / 16
    
    // points to the next location of the newly added Autoreleased object, initialized to begin();
    __unsafe_unretained id *next; / / 8

    // point to the current thread;
    pthread_t const thread; / / 8
    
    // point to the parent, the first parent is nil;
    AutoreleasePoolPage * const parent; / / 8
    
    // points to a child, and the last child is nil;
    AutoreleasePoolPage *child; / / 8
    
    // represents the depth, starting at 0 and increasing by 1;
    uint32_t const depth; / / 4
    
    // Indicates the high water mark
    uint32_t hiwat;  / / 4. struct magic_t {static const uint32_t M0 = 0xA1A1A1A1;
    #   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4]; / / 4 * 4. }};Copy the code

Configure to turn off ARC, let’s test:

As you can see by printing, the sentinel object is also an object.

Let’s go to the push() function:

push()

For the first time,

First there is no autoreleaseNoPage, we create the first page, then make the page hot, then add the pancake object, then add the other objects to the pool.

    static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts with a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        returndest; }...static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if(page && ! page->full()) {// There is a page, the page is not full
            return page->add(obj);
        } else if (page) {
            // There is a page, the page is full
            return autoreleaseFullPage(obj, page);
        } else {
            // No page - created
            returnautoreleaseNoPage(obj); }}...static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // "No page" may mean that the pool has not been pushed
        // Or an empty placeholder pool has been pushed and has no contentsASSERT(! hotPage()); bool pushExtraBoundary =false;
        if (haveEmptyPoolPlaceholder()) { ... }
        else if(obj ! = POOL_BOUNDARY && DebugMissingPools) { ... }else if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) { ... }// We are pushing an object or a pool of non-placeholders

        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page); // Set the page to hot
        
        // Push a boundary for the previous placeholder pool.
        if (pushExtraBoundary) {
            // Add the sentinel object
            page->add(POOL_BOUNDARY); 
        }
        
        // Push the requested object or pool.
        return page->add(obj); 
    }
Copy the code

AutoreleasePoolPage

    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
		AutoreleasePoolPageData(begin(), 
                objc_thread_self(),
                newParent,
                newParent ? 1+newParent->depth : 0,
                newParent ? newParent->hiwat : 0)
    {
        if(objc::PageCountWarning ! = -1) {
            checkTooMuchAutorelease();
        }
        // Here is the bidirectional list setting
        if(parent) { parent->check(); ASSERT(! parent->child); parent->unprotect(); parent->child =this; parent->protect(); } protect(); }... id *begin() {
        // Begin the page starts from memory after the member of the auto-free pool (which also requires 56 bytes of memory space)
        return (id *) ((uint8_t *)this+sizeof(*this)); / / 56
    }
Copy the code

Its size is 56 bytes.

Here, we can also verify that the -begin page starts from memory after the member of the auto-free pool (which also requires 56 bytes of memory space). And the first object added to the automatic release pool is the sentinel object; And then we’re going to push the objects in.

Full page – > full ()

 static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hotspot page is full
        // Step jumps to the next incomplete page, adding a new page if necessary.
        // Then add the object to the page.
        ASSERT(page == hotPage());
        ASSERT(page->full()  ||  DebugPoolAllocation);

        do {
            // When the page is full, find the last subpage before adding a page
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

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

Memory paging management:

  • Routine operation of space for time:

Here do not use continuous memory, is to continuously stack out of the stack of memory pressure and operational performance problems, as well as all the data on a page, management will be very tedious, once a problem, all finished; Therefore, using paging can effectively reduce the impact of mutual descending and improve performance; After paging, memory can be discontinuous, again improving performance.

Automatically release the pool size

We found that each page can manage 504 objects:

Why 504 objects on a page?

504*8+56+POOL_BOUNDARY = 4096 = 2^12

This size is also reflected in the source code:

The process of pushing the object into the stack: after the object is added, it points to the next location through the next pointer.

Out of the stack process: find the hot page, find the parent page set as hot page, delete the current page. See if it’s done by looking at the sentinel object.

extension

  • Automatic release pools can be nested and do not affect each other.
  • The absence of an AutoRelease is not handled by @AutoReleasepool (the compiler adds autoReleases automatically in ARC environments).
  • Objects prefixed with Alloc, new, copy, or Mutablecopy will not be managed by @Autoreleasepool.