In the last article, I looked at the retain and releasae functions of objC_Object. When I looked at rootAutorelease AutoreleasePoolPage, I thought it was time to learn more about automatic release pools. So let’s start with this article.

AutoreleasePool structure diagram:

Write the following function in main.m and use clang-rewrite-objc main.m to convert main.m to main. CPP:

// in the main.m file:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
    }
    
    return 0;
}

// main.cpp
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {

  // constructor
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(a); }// destructor
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); }// Member variables
  void * atautoreleasepoolobj;
};

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

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    }

    return 0;
}

// nsobject. mm file:
// objc_autoreleasePoolPush function implementation
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push(a); }// objc_autoreleasePoolPop function implementation
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code

Code above clearly tells us that automatically release AutoreleasePoolPage: when the pool structure: push () function, when the destructor call AutoreleasePoolPage: : pop (CTXT); Function.

AutoreleasePoolPageData

As always, we first analyze the data structure used (the associated class or structure implementation) and find that this is a good way to start. Here are some steps you can take each time you want to learn more about a topic:

  1. The first step is to find articles related to the content and have a cognition of the general knowledge context. It’s ok even if you can’t understand some parts, even if you can’t read vaguely.
  2. The second step is to go directly to the source code, which is generally simpleC++Code, and thenAppleEncapsulation is also done particularly well, each function each function is particularly clear, look at the source code is the most relaxed and the most clear, then the first step to see the relevant principle will slowly emerge in my mind slowly be connected.
  3. The third step of the source code to read the familiar, and then back to the first step, and then combined with the source code can see some deeper articles, and finally can do through. ⛽ ️ ⛽ ️

Starting with rootAutorelease, holding command we go layer by layer into nsobject-internal.h, Magic_t, AutoreleasePoolPageData, thread_data_t, and the AutoreleasePoolPage forward declaration (class AutoreleasePoolPage, In order to have member variables that use AutoreleasePoolPageData in the AutoreleasePoolPageData definition), it is these that constitute the complete structure of the AutoreleasePoolPageData.

First let’s look at the comments and some macro definitions at the beginning of the nsobject-internal. h file.

Autorelease pool implementation A thread’s Autorelease pool is A stack of Pointers. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. Thread-local storage points to the hot page, where newly autoreleased objects are stored.

A thread’s auto-release pool is a stack of Pointers (the auto-release pool is a bidirectional linked list of AutoreleasePoolPage, and each AutoreleasePoolPage has a stack of Pointers). Each pointer in the stack is either an object waiting for an autorelease or POOL_BOUNDARY automatically releases the pool boundary (actually #define POOL_BOUNDARY nil, which is also a pointer to next). A pool token is a pointer to POOL_BOUNDARY. When the pool is popped, every object hotter than the sentinel is released. When auto-release pool popped, every object is hotter than the sentinel is released. These stacks are scattered in a bidirectional linked list composed of AutoreleasePoolPage. AutoreleasePoolPage is added and removed as needed. Hotpages are stored in the current thread and are added to hotPages when new AutoRelease objects are added to the autorelease pool.

The following macro definition:

  • #define AUTORELEASEPOOL_VERSION 1Automatically release the pool version number only whenABICompatibility changes when it is broken.
  • #define PROTECT_AUTORELEASEPOOL 0Set this to 1 to proceedmprotect()Automatically release the contents of the pool. (mprotect()You can set the protection property of the memory region of the autofree pool to limit the memory region to be readable or writable only.
  • #define CHECK_AUTORELEASEPOOL (DEBUG)Setting this to 1 fully validates the auto-release pool at all timesheader. (i.e.magic_tcheck()fastcheck()When set to 1, use anywhere to verify that all four elements of the full array are equal, or just the first element is equalcheck()Instead offastcheck(), can be seen inDebugState is complete validation, other cases are quick validation)

WARNING DANGER HAZARD BEWARE EEK tells us that any contents of this file are for internal use by Apple and may change the contents of the file in unpredictable ways in any version update.

struct magic_t

struct magic_t {
    // Static immutable 32-bit int value
    static const uint32_t M0 = 0xA1A1A1A1;
    
    // This macro, emm....
#   define M1 "AUTORELEASE!"
    
    // The m array takes up 16 bytes, each uint32_t takes up 4 bytes, subtracting the 4 of the first element leaves 12
    static const size_t M1_len = 12;
    
    // Uint32_t array of length 4
    uint32_t m[4];

    // the magic_t constructor
    magic_t() {
        / / is 12
        ASSERT(M1_len == strlen(M1));
        // 12 = 3 * 4
        ASSERT(M1_len == 3 * sizeof(m[1]));

        // the first element of the m array is M0
        m[0] = M0;
        // copy M1 to the next 12 bytes from m[1]
        // The first 4 bytes will be the number M0 and the last 12 bytes will be the string AUTORELEASE!
        strncpy((char *)&m[1], M1, M1_len);
    }
    
    // destructor
    ~magic_t() {
        // Clear magic before deallocation.
        // magic_t clears data before dealLocation.
        
        // This prevents some false positives in memory debugging tools.
        // This prevents the memory debugger from generating false positives.
        
        // fixme semantically this should be memset_s(), 
        // but the compiler doesn't optimize that at all (rdar://44856676).
        Semantically, this should be memset_s (), but the compiler doesn't optimize it at all.
        
        // Convert m to an array of uint64_t with 8 bytes of uint64_t,
        // Convert an array of 4 elements (4 bytes per element) to 2 elements (8 bytes per element).
        volatile uint64_t *p = (volatile uint64_t *)m;
        
        // set 16 bytes to 0
        p[0] = 0; p[1] = 0;
    }

    bool check(a) const {
        / / testing
        // The 0 element is M0 and the following 12 bytes are M1, which returns true if the value is exactly the same as initialized in the constructor
        return (m[0] == M0 && 0= =strncmp((char *)&m[1], M1, M1_len));
    }

    bool fastcheck(a) const {
#if CHECK_AUTORELEASEPOOL
        // The program performs a complete comparison in DEBUG mode
        return check(a);#else
        // The program only compares m[0] 0xA1A1A1A1 in RELEASE mode
        return (m[0] == M0);
#endif
    }

// M1 undefines macros
#   undef M1
};
Copy the code

struct AutoreleasePoolPageData

AutoreleasePoolPage is a private class inherited from AutoreleasePoolPageData.
// In AutoreleasePoolPageData to declare a member variable of type AutoreleasePoolPage,
// The two Pointers used in the bidirectional list parent and child.
class AutoreleasePoolPage;

struct AutoreleasePoolPageData
{
    // struct magic_t as header of AutoreleasePoolPage to validate AutoreleasePoolPage
    // 0xA1A1A1A1AUTORELEASE!
    magic_t const magic;
    
    // __unsafe_unretained modifier next, but still retains the modifier.
    // The next pointer acts as a cursor to the position next to the newly added AutoRelease at the top of the stack
    __unsafe_unretained id *next;
    
    // typedef __darwin_pthread_t pthread_t;
    // typedef struct _opaque_pthread_t *__darwin_pthread_t;
    Struct _opaque_pthread_t;
    // AutoreleasePool is thread-to-thread. Thread is the thread in which the pool is automatically released.
    pthread_t const thread;
    
    // AutoreleasePool does not have a single structure. Instead, it consists of several autoreleasepoolPages combined in a bidirectional linked list.
    // The parent and child AutoreleasePoolPage Pointers are used to form lists.
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    
    // Mark the depth of each pointer. For example, the depth of the first page is 0, and the depth of subsequent pages increases
    uint32_t const depth;
    
    // high-water
    uint32_t hiwat;

    // constructor
    Parent is initialized according to _parent, and child is initialized to nil
    // The parent and child of the first page are nil
    // The second page is then initialized with the first page passed as its parent
    // Then the first page's child points to the second page, and the parent points to nil
    // The parent of the second page points to the first page, and the child points to nil
    AutoreleasePoolPageData(__unsafe_unretained id* _next,
                            pthread_t _thread,
                            AutoreleasePoolPage* _parent,
                            uint32_t _depth,
                            uint32_t _hiwat) : magic(),
                                               next(_next),
                                               thread(_thread),
                                               parent(_parent),
                                               child(nil),
                                               depth(_depth),
                                               hiwat(_hiwat){
    }
};
Copy the code

struct thread_data_t

struct thread_data_t
{
#ifdef __LP64__
    pthread_t const thread; // The actual type of pthread_t is a pointer to struct _opaque_pthread_t, which is 8 bytes long
    uint32_t const hiwat; / / 4 bytes
    uint32_t const depth; / / 4 bytes
#else
    pthread_t const thread;
    uint32_t const hiwat;
    uint32_t const depth;
    uint32_t padding;
#endif
};

// An assertion that will be executed if thread_data_t size is not 16
Thread_data_t size is 8 + 4 + 4 = 16 on the __LP64__ platform
C_ASSERT(sizeof(thread_data_t) = =16);
Copy the code

AutoreleasePoolPage

Now let’s start reading AutoreleasePoolPage source code.

BREAKPOINT_FUNCTION

The BREAKPOINT_FUNCTION macro is defined in objC-os. h and is defined differently for different runtime environments. Defined in TARGET_OS_MAC as follows:

/* Use this for functions that are intended to be breakpoint hooks. If you do not, the compiler may optimize them away. BREAKPOINT_FUNCTION( void stop_on_error(void) ); // If we want to breakpoint a function, we need to declare it with this macro definition. // If we do not, the compiler may optimize them. // For example, BREAKPOINT_FUNCTION(void stop_on_error(void)); * /
#   define BREAKPOINT_FUNCTION(prototype)                             \
    OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
    prototype { asm(""); }
Copy the code

attribute((used))

__attribute__((used))

  1. Used to tell the compiler to keep a static function or variable in the object file, even if it is not referenced.
  2. Marked asattribute__((used))The function is marked in the object file to avoidThe linkerDelete unused sections.
  3. A static variableYou can also label itused, the method is to useattribute((used)).
  4. useusedField, even without any references, inReleaseIt will not be optimized.

For details about the possible causes of the warning message, see the following: Attribute ((used)) usage problem

// Indicates that the function or variable may not be used. This property prevents the compiler from generating warning messages
#define __attribute_unused__ __attribute__((__unused__))

// Tell the compiler that this code is useful, even if it is not used
#define __attribute_used__ __attribute__((__used__))
Copy the code

attribute((visibility(“hidden”)))

In the dynamic library (.so) under Linux, you can control shared file export symbols through the C++ property visibility of GCC. In GCC 4.0 and above, there is a property called visibility that can be applied to functions, variables, templates, and C++ classes.

Reasons for limiting symbol visibility: It is a good practice to output as few symbols as possible from dynamic libraries. Printing a restricted symbol improves the modularity of the program and hides implementation details. The fewer symbols the dynamic library loads and recognizes, the faster the program gets up and running. Exporting all symbols slows the program down and uses a lot of memory. Default: Symbols defined with it are exported, and functions in the dynamic library are visible by default. Hidden: Symbols defined with it are not exported and cannot be used from other objects. Functions in the dynamic library are hidden. Default means that the method is visible to other modules. Hidden indicates that the method symbol will not be placed in the dynamic symbol table, so other modules (executables or dynamic libraries) cannot access the method through the symbol table. To define GNU attributes, you need to include attributes and parenthesized content. The visibility of symbols can be specified as visibility(” hidden “), which will not allow them to be exported in the library, but can be shared between source files. In fact, hidden symbols do not appear in the dynamic symbol table, but are left in the symbol table for static linking. The export list is automatically generated by the compiler when creating the shared library, or it can be written manually by the developer. The idea behind an export list is to explicitly tell the compiler which symbols can be exported from an object file via an external file. GNU users refer to such external files as export maps. Use __attribute__((visibility (“default”))) in Linux

Defined in the TARGET_OS_WIN32 environment as follows:

/* Use this for functions that are intended to be breakpoint hooks. If you do not, the compiler may optimize them away. BREAKPOINT_FUNCTION( void MyBreakpointFunction(void) ); * /
   // same as 👆
   // Call the function noinline.
#   define BREAKPOINT_FUNCTION(prototype) \
    __declspec(noinline) prototype { __asm { } }
Copy the code

Modify the following two functions with BREAKPOINT_FUNCTION:

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

AutoreleasePoolPage is a class that privately inherits from AutoreleasePoolPageData. Thread_data_t is a friend structure of AutoreleasePoolPage that provides direct access to AutoreleasePoolPage’s private member variables.

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;. };Copy the code

SIZE

Indicates the capacity of AutoreleasePoolPage. If PROTECT_AUTORELEASEPOOL is 0 in nsobject-internal. H, then the value of SIZE is PAGE_MIN_SIZE. (in vm_param.h PAGE_MAX_SIZE and PAGE_MIN_SIZE are both 4096…)

    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif

#define PAGE_MIN_SIZE           PAGE_SIZE
#define PAGE_SIZE               I386_PGBYTES
#define I386_PGBYTES            4096            /* bytes per 80386 page */
Copy the code

As you can see, the value of SIZE is 4096 bytes (the Pointers to the saved AutoRelease object are 8 bytes each).

private:
    // typedef __darwin_pthread_key_t pthread_key_t;
    // typedef unsigned long __darwin_pthread_key_t;
    So pthread_key_t is actually an unsigned long type
    
    // #define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
    // typedef pthread_key_t tls_key_t;
    // #define __PTK_FRAMEWORK_OBJC_KEY3 43
    
    // AUTORELEASE_POOL_KEY 
    // Thread Local Storage is used to store data in the current Thread.
    // The data is stored and read through these fixed keys.
    
    // Retrieve the hotPage from the current thread's store with this key
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    
    // SCRIBBLE
    // In releaseUntil, objc_object ** in page executes objc_release on the object pointed to.
    // SCRIBBLE will be placed in the empty space
    // Release objc_objcect **
    // SCRIBBLE where objc_object ** was stored
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    
    // The number of ids that can be saved 4096/8 = 512 (actual available capacity is 4096 minus 56 bytes occupied by member variables)
    static size_t const COUNT = SIZE / sizeof(id);

    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one 
    // pool is pushed and it has never contained any objects. 
    EMPTY_POOL_PLACEHOLDER is stored in TLS when an autofree pool is created and no objects are put into it.
    
    // This saves memory when the top level (i.e. libdispatch) 
    // pushes and pops pools but never uses them.
    Memory is saved when Top level(such as libdispatch) and pools are not using them.
    
    // Convert 1 to objc_object **
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

    // Pool bounds are nil
#   define POOL_BOUNDARY nil

// SIZE-sizeof(*this) bytes of contents follow
Copy the code

new/delete

// Allocate space and align memory

static void * operator new(size_t size) {
    // extern malloc_zone_t *malloc_default_zone(void); /* The initial zone */ /
    // extern void *malloc_zone_memalign(malloc_zone_t *zone,
    // size_t alignment,
    // size_t size)
    // __alloc_size(3) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0);
    // Alignment Indicates the alignment length
    // Assign a new pointer of size whose address is an exact multiple of alignment.
    // The alignment must be a power of two and at least as large as sizeof(void *). The zone must be non-null.
    return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
static void operator delete(void * p) {
    // Free memory
    return free(p);
}
Copy the code

protect/unprotect

We know that PROTECT_AUTORELEASEPOOL has a value of 0 in nsobject-internal. H, so these two functions currently do nothing.

    inline void protect(a) {
#if PROTECT_AUTORELEASEPOOL
        // Memory areas of SIZE from this are only readable
        mprotect(this, SIZE, PROT_READ);
        check(a);#endif
    }

    inline void unprotect(a) {
#if PROTECT_AUTORELEASEPOOL
        check(a);// The SIZE of the inner coarse area from this is readable and writable
        mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
    }
Copy the code

In Linux, the mProtect () function can be used to modify the protection properties of a specified memory region. For example, specify an area that is readable only, writable only, read-writable, etc. The function prototype is as follows:

#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);
Copy the code

The mprotect() function changes the len protection attribute of the memory area since start to the value specified by prot. Prot can take the following values, and can be combined with | several properties using:

  1. PROT_READ: indicates that the contents of the memory segment are readable.
  2. PROT_WRITE: indicates that the contents of the memory segment are writable.
  3. PROT_EXEC: indicates that the contents of the memory segment are executable.
  4. PROT_NONE: indicates that the contents of the memory segment are not accessible at all.

It is important to note that the specified memory range must contain the entire memory page (typically 4Kb, depending on the architecture and operating system, the size of a page varies. How do I get the page size? With the PAGE_SIZE macro or the getPagesize () system call). Start must be the start address of a memory page, and len must be an integer multiple of the page size. If the execution succeeds, 0 is returned; If the execution fails, -1 is returned. For details, see the mprotect function usage

AutoreleasePoolPage(AutoreleasePoolPage *newParent)/~AutoreleasePoolPage()

AutoreleasePoolPage *newParent; AutoreleasePoolPage *newParent; AutoreleasePoolPage; The member variables AutoreleasePoolPage * const parent and AutoreleasePoolPage *child are used as Pointers to the parent and child nodes. (parent is the first, child is the second)

The constructor of AutoreleasePoolPageData shows that the parent and child of the first node are nil. When the first AutoreleasePoolPage is full, an AutoreleasePoolPage is created. The second node is built with the first node as the newParent parameter, with the child of the first node pointing to the second node and the parent of the second node pointing to the first node.

AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    AutoreleasePoolPageData(begin(),
                            // The current thread, this thread access function is very complex, the following will be analyzed in detail
                            objc_thread_self(),
                            // parent 
                            newParent,
                            // The depth of the first node is 0.
                            // The second node is 1, and the third node is 2
                            newParent ? 1+newParent->depth : 0.// high-water
                            newParent ? newParent->hiwat : 0)
{ 
    if (parent) {
        // Check whether the parent node is compliant, check magic and thread
        parent->check(a);// The child of the parent node must be nil because the currently created page is the child of the parent
        ASSERT(! parent->child);// Can read and write
        parent->unprotect(a);// take the current node as the child node of the entry parameter newParent
        parent->child = this;
        
        / / can only be read
        parent->protect(a); }/ / can only be read
    protect(a); }// destructor
~AutoreleasePoolPage() 
{
    / / check
    check(a);// Can read and write
    unprotect(a);// If there is no autoRelease object in the page, perform the assertion
    ASSERT(empty());

    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    
    // child points to nil otherwise perform assertion
    ASSERT(! child); }Copy the code

AutoreleasePoolPage must satisfy empty() and child pointing to nil, magic.check() must be true, and thread == objc_thread_self(), Only when these four conditions are met at the same time can the destruct be normal.

busted/busted_die

// It can be _objc_FATAL or _objc_inform depending on the log parameter
template<typename Fn>
void busted(Fn log) const {
    // a full default value for the magic_t variable
    magic_t right;
    
    // log
    log("autorelease pool page %p corrupted\n"
         " magic 0x%08x 0x%08x 0x%08x 0x%08x\n"
         " should be 0x%08x 0x%08x 0x%08x 0x%08x\n"
         " pthread %p\n"
         " should be %p\n".this, 
         magic.m[0], magic.m[1], magic.m[2], magic.m[3], 
         right.m[0], right.m[1], right.m[2], right.m[3].this->thread, objc_thread_self());
}

__attribute__((noinline, cold, noreturn))
void busted_die(a) const {
    // Execute _objc_FATAL printing
    busted(_objc_fatal);
    __builtin_unreachable();
}
Copy the code

check/fastcheck

Check whether magic is equal to the default value and check the current thread, then log passes _objC_inform or _objC_FATAL calls busted.

    inline void
    check(bool die = true) const
    {
        if(! magic.check() || thread ! =objc_thread_self()) {
            if (die) {
                busted_die(a); }else {
                busted(_objc_inform); }}}inline void
    fastcheck(a) const
    {
    #define CHECK_AUTORELEASEPOOL (DEBUG) #define CHECK_AUTORELEASEPOOL (DEBUG
#if CHECK_AUTORELEASEPOOL
        check(a);#else
        // If magic.fastCheck () fails, busted_die is executed
        if (! magic.fastcheck()) {
            busted_die(a); }#endif
    }
Copy the code

begin/end/empty/full/lessThanHalfFull

begin

The begin function is critical. Begin is the starting point for the autorelease objects stored in AutoreleasePoolPage. In addition to storing AutoreleasePoolPage member variables, the system allocates 4096 bytes of AutoreleasePoolPage member variables. The remaining space is used to store the address of the auto-free object.

AutoreleasePoolPage member variables are inherited from AutoreleasePoolPageDate. They require 56 bytes of space, and then 4040 bytes of space is left. An object pointer takes up 8 bytes. An AutoreleasePoolPage can hold 505 objects that need to be released automatically. (You can include #include “nsobject-internal. h” in main.m to print that sizeof(AutoreleasePoolPageData) is indeed 56.)

id * begin(a) {
    // (uint8_t *)this is the start address of AutoreleasePoolPage
    Uint8_t * = uint8_t = 1 bytes
    // Then make sure (uint8_t *)this adds 56 bytes forward
    
    // sizeof(*this) is AutoreleasePoolPage the width of all member variables is 56 bytes,
    // Returns the memory address 56 bytes forward from the start of the page.
    return (id *) ((uint8_t *)this+sizeof(*this));
}
Copy the code

end

id * end(a) {
    // (uint8_t *)this starts as the uint8_t pointer
    // Then advance SIZE bytes to the end of the AutoreleasePoolPage
    return (id *) ((uint8_t *)this+SIZE);
}
Copy the code

empty

The next pointer usually points after the last auto-release object in the current auto-release pool. If next points to the position of BEGIN, there are no auto-release objects in the current auto-release pool.

bool empty(a) {
    return next == begin(a); }Copy the code

full

Next points to the position of end, indicating that the auto-free pool is full of objects that need to be auto-freed.

bool full(a) { 
    return next == end(a); }Copy the code

lessThanHalfFull

Indicates whether the current capacity of the automatic release pool is less than half of the total capacity. The distance between next and BEGIN is the number of automatically released objects currently stored. The distance between End and BEGIN is the total capacity of automatically released objects that can be stored.

bool lessThanHalfFull(a) {
    return (next - begin() < (end() - begin/ ())2);
}
Copy the code

add

Put an AutoRelease object into the autorelease pool.

id *add(id obj)
{
    // If the auto-release pool is full, perform an assertion
    ASSERT(!full());
    
    // Can read and write
    unprotect(a);// Record the current point to next as the return value of the function. What aliasing means is faster than 'return next-1'
    id *ret = next;  // faster than `return next-1` because of aliasing
    
    // Next is an objc_object **, the dereference operator * is used to fetch objc_object *,
    // Then assign obj to it, and next will do an increment 8 bytes forward to the next location.
    *next++ = obj;
    
    / / can only be read
    protect(a);// ret is now pointing to obj. (obj is a pointer to objc_Object, not objc_Object)
    return ret;
}
Copy the code

releaseAll/releaseUntil

void releaseAll(a) 
{
    // Call releaseUntil and pass begin,
    // Start from next and move backwards until begin,
    // Perform an objc_release on all autofree objects between begin and next
    releaseUntil(begin());
}
Copy the code

Starting from Next and moving backwards until you reach Stop, perform an objC_release on all autofree objects on the path.

    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        // The loop starts at next and continues backwards until next reaches stop
        while (this->next ! = stop) {// Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            
            // Get the current AutoreleasePoolPage
            AutoreleasePoolPage *page = hotPage(a);// fixme I think this `while` can be `if`, but I can't prove it
            I think "while" can be "if", but I can't prove it
            // We can use if instead of while
            // When a page is full, a new page is generated and linked to the next page.
            // So from the first page to the previous hotPage, it should be full
            
            // If the current page is empty, step back and make the previous AutoreleasePoolPage hotPage
            while (page->empty()) {
                // The page is empty, and the page is not stopped.
                / / go back
                page = page->parent;
                // set page as hotPage
                setHotPage(page);
            }
            
            // Can read and write
            page->unprotect(a);// next moves a step back and uses the dereference to fetch objc_object * and assign it to obj
            id obj = *--page->next;
            
            // SCRIBBLE the sizeof(*page->next) bytes starting from page->next
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            
            / / can only be read
            page->protect(a);// If obj is not nil, perform objc_release
            if(obj ! = POOL_BOUNDARY) {objc_release(obj); }}// This is the hotPage,
        // SCRIBBLE may be placed from the stop page to the hotPage where the auto-release object was stored
        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        // Make sure the page is empty starting from the child of the current page
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
#endif
    }
Copy the code

Start from the previous page and move backwards until you reach the stop page, and execute an objc_release on all objects saved in the passing page. SCRIBBLE each space where the objc_Object ** was stored. Next for each page points to begin for that page.

I have a question here, can this and hotPage be the same page?

kill

What release does is iterate over the saved autorelease object, and what kill does is iterate over the delete operation on AutoreleasePoolPage.

void kill(a) 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    
    AutoreleasePoolPage *page = this;
    // Follow the child chain from the current page to the last page of AutoreleasePool's bidirectional linked list
    while (page->child) page = page->child;

    // Temporary variable (death pointer)
    AutoreleasePoolPage *deathptr;
    
    // It is a do while loop, so it will delete at least once,
    // The current page will also be delete (unlike the release operation above, the stop parameter will not be objc_release)
    do {
        // The page to delete
        deathptr = page;
        
        // Record the previous page
        page = page->parent;
        
        // If the current page parent exists, set the parent's child to nil
        // This is a classic operation of the linked list algorithm
        if (page) {
            // Can read and write
            page->unprotect(a);// Set child to nil
            page->child = nil;
            
            / / write
            page->protect(a); }// delete page
        delete deathptr;
    } while(deathptr ! =this);
}
Copy the code

Start with the current page and continue through the child chain until the child is empty. Delete all passing pages (including the current page).

tls_dealloc

When Thread Local Stroge dealloc is executed, release all the auto-release objects in the auto-release pool and kill all the pages.

static void tls_dealloc(void *p) 
{
    // # define EMPTY_POOL_PLACEHOLDER ((id*)1)
    // return if p is an empty placeholder pool
    if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
        // No objects or pool pages to clean up here.
        // There are no objects or pages to clean up
        return;
    }

    // reinstate TLS value while we work
    // save p in TLS as hotPage
    setHotPage((AutoreleasePoolPage *)p);

    if (AutoreleasePoolPage *page = coldPage()) {
        // If coldPage exists (the first page in the bidirectional list)
        
        // This call has a super long chain of functions, which ultimately implements all auto-release objects in the auto-release pool
        // objc_release then delete all pages
        if(! page->empty()) objc_autoreleasePoolPop(page->begin());  // pop all of the pools
        
        // OPTION( DebugMissingPools, 
        // OBJC_DEBUG_MISSING_POOLS,
        // "warn about autorelease with no pool in place, which may be a leak")
        // Warning that there is no automatic release of the pool, which could be a leak
        
        // OPTION( DebugPoolAllocation,
        // OBJC_DEBUG_POOL_ALLOCATION,
        // "halt when autorelease pools are popped out of order,
        // and allow heap debuggers to track autorelease pools")
        // Pause when the auto release pool pops up sequentially, and allow the heap debugger to track the auto release pool
        
        if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
            // pop() killed the pages already
        } else {
            // Delete all pages from page up to child
            // kill processes only page, not autoRelease objects
            page->kill(a);// free all of the pages}}// clear TLS value so TLS destruction doesn't loop
    // Clear TLS values so that TLS destruction does not loop
    // set hotPage to nil
    // static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    // tls_set_direct(key, (void *)page);
    // Set key to nil
    setHotPage(nil);
}
Copy the code

pageForPointer

Void *p converts a pointer to begin() to AutoreleasePoolPage *.

static AutoreleasePoolPage *pageForPointer(const void *p) 
{
    // The pointer is converted to unsigned long
    return pageForPointer((uintptr_t)p);
}

static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
{
    // result temporary variable
    AutoreleasePoolPage *result;
    
    // malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    // memory alignment is based on SIZE, so the start address of each page must be an integer multiple of SIZE
    // p = 1024
    uintptr_t offset = p % SIZE;

    // Modulo 4096, offset should be between 0 and 4095
    Sizeof (AutoreleasePoolPage) should have the same value as sizeof(AutoreleasePoolPageData), both 56
    // The p entry is at least from page begin(), so at least from page start 56,
    // So the offset is in the range of [56 4095]
    ASSERT(offset >= sizeof(AutoreleasePoolPage));

    // p subtracts offset, p goes back to the starting point of page
    result = (AutoreleasePoolPage *)(p - offset);
    
    // Check if result is magic.check() and thread == objc_thread_self()
    result->fastcheck(a);return result;
}
Copy the code

haveEmptyPoolPlaceholder/setEmptyPoolPlaceholder

Each thread has its own storage space. In this case, an empty pool is stored in the current thread’s storage space by key.

// Two static inline functions
static inline bool haveEmptyPoolPlaceholder(a)
{
    // Key is a static local variable
    // static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    // # define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
    // # define EMPTY_POOL_PLACEHOLDER ((id*)1)
    
    // The current thread finds an empty pool based on the key
    id *tls = (id *)tls_get_direct(key);
    
    // Return false if not found
    return (tls == EMPTY_POOL_PLACEHOLDER);
}

static inline id* setEmptyPoolPlaceholder(a)
{
    // The current thread does not store the contents of the key, otherwise the assertion is executed
    // The original value will be overwritten, so you must ensure that no page is now stored under the key
    ASSERT(tls_get_direct(key) == nil);
    
    // Store EMPTY_POOL_PLACEHOLDER in the current thread's storage space by key
    tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
    
    EMPTY_POOL_PLACEHOLDER, ((id *)1)
    return EMPTY_POOL_PLACEHOLDER;
}
Copy the code

hotPage/setHotPage

static inline AutoreleasePoolPage *hotPage(a) 
{
    // The current hotPage is stored in the current thread's storage space by key
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)tls_get_direct(key);
    
    // return nil if equal to EMPTY_POOL_PLACEHOLDER
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    
    // result Run the check command to check whether the AutoreleasePoolPage restriction rules are met
    if (result) result->fastcheck(a);return result;
}

static inline void setHotPage(AutoreleasePoolPage *page) 
{
    // Page check to determine whether the AutoreleasePoolPage Magic constraint rules are met
    if (page) page->fastcheck(a);// Store the page in the current thread's storage space according to the key, as hotPage
    tls_set_direct(key, (void *)page);
}
Copy the code

coldPage

“Cold” Page, first finds hotPage and then follows its parent until parent is nil, and the last AutoreleasePoolPage is coldPage, and returns it. ColdPage is the first page in the bidirectional page list.

static inline AutoreleasePoolPage *coldPage(a) 
{
    // hotPage
    AutoreleasePoolPage *result = hotPage(a);if (result) {
        // Loop along the parent pointer until the first AutoreleasePoolPage
        while (result->parent) {
            // Update result along parent
            result = result->parent;
            
            // Check that result matches the page rule
            result->fastcheck();
        }
    }
    return result;
}
Copy the code

autoreleaseFast

Put objects quickly into the automatic release pool.

static inline id *autoreleaseFast(id obj)
{
    // hotPage
    AutoreleasePoolPage *page = hotPage(a);if(page && ! page->full()) {
        // If the page exists and the page is not full, call add directly to add obj to the page
        return page->add(obj);
    } else if (page) {
        // If the page is full, call autoreleaseFullPage to build a new AutoreleasePoolPage and add obj to it
        return autoreleaseFullPage(obj, page);
    } else {
        // Even hotPage does not exist, it is possible to hold an EMPTY_POOL_PLACEHOLDER in the thread's storage space
        // If the page does not exist, i.e. the current thread does not already have an AutoreleasePoolPage, build a new AutoreleasePoolPage and add obj to it
        return autoreleaseNoPage(obj); }}Copy the code

autoreleaseFullPage

static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    // The hot page is full. 
    // Step to the next non-full page, adding a new page if necessary.
    // Then add the object to that page.
    // If the hotpage is full, go to the next full page, and add a new page if it doesn't exist.
    // Then add object to the new page.
    
    // Page must be hotPage
    ASSERT(page == hotPage());
    // The page is full, or the auto-release pool is paused when it pops up in sequence, allowing the heap debugger to track the auto-release pool
    
    // OPTION( DebugPoolAllocation,
    // OBJC_DEBUG_POOL_ALLOCATION,
    // "halt when autorelease pools are popped out of order,
    // and allow heap debuggers to track autorelease pools")
    // The automatic release pool is paused when it pops up in sequence, allowing the heap debugger to track the automatic release pool
    
    ASSERT(page->full()  ||  DebugPoolAllocation);

    // There are two cases in the do while loop
    // 1. Follow child, if you can find a non-full page, put obj in it
    // 2. If child does not exist or all children are full,
    // Create a new AutoreleasePoolPage and concatenate it into AutoreleasePool's bidirectional linked list.
    // Add obj to the new page
    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    // Set page to hotPage
    setHotPage(page);
    
    // add obj to the page and return the position pointed to before next (objc_object **)
    return page->add(obj);
}
Copy the code

autoreleaseNoPage

static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
    // "No page" could mean no pool has been pushed or an empty
    // placeholder pool has been pushed and has no contents yet
    // "No page" may mean that No pools are built, or that there is only an EMPTY_POOL_PLACEHOLDER placeholder
    
    // hotPage does not exist, otherwise perform assertion
    ASSERT(!hotPage());

    bool pushExtraBoundary = false;
    if (haveEmptyPoolPlaceholder()) {
        // if EMPTY_POOL_PLACEHOLDER is stored in the thread
        
        // We are pushing a second pool over the empty placeholder pool
        // or pushing the first object into the empty placeholder pool.
        // We are pushing the second pool into an empty placeholder pool, or the first object into an empty placeholder pool.
        // Before doing that, push a pool boundary on behalf of the
        // pool that is currently represented by the empty placeholder.
        // Until then, pool boundaries are pushed on behalf of the pool currently represented by an empty placeholder
        
        pushExtraBoundary = true;
    }
    else if(obj ! = POOL_BOUNDARY && DebugMissingPools) {// OPTION( DebugMissingPools, OBJC_DEBUG_MISSING_POOLS,
        // "warn about autorelease with no pool in place, which may be a leak")
        // Warning to autorelease without autorelease pool,
        // This can cause a memory leak (possibly because the pool was not freed and then the object missed an objc_release execution, causing a memory leak)
        // If obj is not nil and DebugMissingPools.
        
        // We are pushing an object with no pool in place,
        // and no-pool debugging was requested by environment.
        // We are pushing an object into the pool without automatically releasing the pool,
        // The environment no-pool debugging is enabled, and a prompt message is displayed on the console.
        
        // EMPTY_POOL_PLACEHOLDER is not stored in the thread, and if DebugMissingPools is open, the console outputs the following information
        _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug".objc_thread_self(), (void*)obj, object_getClassName(obj));
                     
        // obj is not nil, and the thread does not even store EMPTY_POOL_PLACEHOLDER
        // Execute objc_autoreleaseNoPool and it is a hook function
        objc_autoreleaseNoPool(obj);
        
        / / returns nil
        return nil;
    }
    else if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) {// OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION, 
        // "halt when autorelease pools are popped out of order,
        // and allow heap debuggers to track autorelease pools")
        // Pause when the auto release pool pops up sequentially, and allow the heap debugger to track the auto release pool
        // If obj is empty and DebugPoolAllocation is not enabled
        
        // We are pushing a pool with no pool in place,
        // and alloc-per-pool debugging was not requested.
        // In the case of no pool, we set an empty pool placeholder and do not require space allocation and debugging for the pool. (Empty pool placeholder is just one ((id*)1))
        
        // Install and return the empty pool placeholder.
        
        // Store the EMPTY_POOL_PLACEHOLDER placeholder in the current thread's storage space based on the key
        return setEmptyPoolPlaceholder(a); }// We are pushing an object or a non-placeholder'd pool.
    // Build a non-placeholder pool

    // Install the first page.
    // Build the first real page of the auto-release pool
    
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    
    // Set it to hotPage
    setHotPage(page);
    
    // Push a boundary on behalf of the previously-placeholder'd pool.
    // Represents the pool push boundary of the previous placeholder.
    
    // If there was an EMPTY_POOL_PLACEHOLDER placeholder in the current thread's storage space
    if (pushExtraBoundary) {
        // Further to the pool boundary
        
        // Push the next pointer forward and drop a nil to the pointer before next
        page->add(POOL_BOUNDARY);
    }
    
    // Push the requested object or pool.
    // Put objc into the auto-release pool
    return page->add(obj);
}
Copy the code

autoreleaseNewPage

static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
    AutoreleasePoolPage *page = hotPage(a);// If hotPage exists, call autoreleaseFullPage to put obj into the page
    if (page) return autoreleaseFullPage(obj, page);
    // If hotPage does not exist, call autoreleaseNoPage to add obj to autorelease pool.
    else return autoreleaseNoPage(obj);
}
Copy the code

Enter the public section of AutoreleasePoolPage:

autorelease

static inline id autorelease(id obj)
{
    // Perform an assertion if the object does not exist
    ASSERT(obj);
    
    // Perform an assertion if the object is Tagged Pointer
    ASSERT(! obj->isTaggedPointer());
    
    // Call the autoreleaseFast(obj) function to quickly put obj into the automatic release pool
    id *dest __unused = autoreleaseFast(obj);
    
    // 1. if (obj ! = POOL_BOUNDARY && DebugMissingPools) return nil
    // 2. if (obj == POOL_BOUNDARY && ! Return EMPTY_POOL_PLACEHOLDER DebugPoolAllocation)
    // 3. *dest == obj
    
    ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);return obj;
}
Copy the code

push

If the auto-release pool does not exist, build a new page. The effect of push function can be understood as, call AutoreleasePoolPage: : push in the current thread a EMPTY_POOL_PLACEHOLDER save storage space.

If the autoreleaseFast function has one more page than the autoreleaseNewPage, it adds obj to the page directly. The remaining calls to autoreleaseFullPage or autoreleaseNoPage are the same.

static inline void *push(a) 
{
    id *dest;
    if (slowpath(DebugPoolAllocation)) {
        // OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION,
        // "halt when autorelease pools are popped out of order, 
        // and allow heap debuggers to track autorelease pools")
        // Stops when the automatic release pool pops up in order and allows the heap debugger to track the automatic release pool
        
        // Each autorelease pool starts on a new pool page.
        // Each autorelease pool starts with a new page
        / / call autoreleaseNewPage
        dest = autoreleaseNewPage(POOL_BOUNDARY);
        
    } else {
    
        // Build a placeholder pool
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    
    return dest;
}
Copy the code

badPop

__attribute__((noinline, cold))
static void badPop(void *token)
{
    // Error. For bincompat purposes this is not fatal in executables built with old SDKs.
    // For bin compatibility purposes, cannot be built and executed on older SDKs, otherwise _objc_fatal.

    if (DebugPoolAllocation || sdkIsAtLeast(10_12.10_0, 10_0, 3_0, 2_0)) {
        // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal.
        // Automatic release pool with invalid or premature release.
        _objc_fatal("Invalid or prematurely-freed autorelease pool %p.", token);
    }

    // Old SDK. Bad pop is warned once.
    // If it is an old SDKs, a warning occurs.
    
    // Static local variables, ensure that the following if can only be entered once
    static bool complained = false;
    if(! complained) { complained =true; / / set to true
        _objc_inform_now_and_on_crash
            ("Invalid or prematurely-freed autorelease pool %p. "
             "Set a breakpoint on objc_autoreleasePoolInvalid to debug. "
             "Proceeding anyway because the app is old "
             "(SDK version " SDK_FORMAT "). Memory errors are likely.",
                 token, FORMAT_SDK(sdkVersion()));
    }
    
    // Execute the original hook
    objc_autoreleasePoolInvalid(token);
}
Copy the code

popPage/popPageDebug

// There is a template argument (allowDebug of type bool),
// Pass values directly, similar to the template parameters of new and old values in sotreWeak
// The void *token argument does not use.... in the function implementation
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    // OPTION( PrintPoolHiwat, OBJC_PRINT_POOL_HIGHWATER, 
    // "log high-water marks for autorelease pools")
    // Prints the high-water flag for the automatic release pool
    // If debug is enabled and OBJC_PRINT_POOL_HIGHWATER is enabled, print the hiwat of the automatic release pool (high-water "highest water").
    if (allowDebug && PrintPoolHiwat) printHiwat(a);// Perform an objc_release operation on all objects added to the automatic release pool after stop
    page->releaseUntil(stop);

    // memory: delete empty children
    // Delete the empty page
    // OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION,
    // "halt when autorelease pools are popped out of order, 
    // and allow heap debuggers to track autorelease pools")
    // Stops when the automatic release pool pops up in order and allows the heap debugger to track the automatic release pool

    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
        // If Debug is enabled and DebugPoolAllocation is enabled and the page is empty
        
        // special case: delete everything during page-per-pool debugging
        // Special case: Delete all content during each page debugging

        AutoreleasePoolPage *parent = page->parent;
        
        // Delete all pages added after the page
        page->kill(a);// Set page parent to hotPage
        setHotPage(parent);
    } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        //OPTION( DebugMissingPools, OBJC_DEBUG_MISSING_POOLS, 
        // "warn about autorelease with no pool in place, which may be a leak")
        // The warning automatically releases without pool placeholder, which could be a leak

        // special case: delete everything for pop(top) when debugging missing autorelease pools
        // Delete all contents of pop (top) when debugging is missing auto-release pool

        // Delete all pages added after the page
        page->kill(a);// Set hotPage to nil
        setHotPage(nil);
    } else if (page->child) {
        // If the page's child exists
        
        // hysteresis: keep one empty child if page is more than half full
        // If the page stores more than half of the autofree objects, leave an empty child
        
        if (page->lessThanHalfFull()) {
            // If less than half of the autofree objects are saved inside the page
            
            // Delete all pages added after the page
            page->child->kill(a); }else if (page->child->child) {
            // If the child of page's child exists
            // Delete page->child->child and all pages added after it
            page->child->child->kill(a); }}}Copy the code
// __attribute__((cold)) indicates that the function is not called often
__attribute__((noinline, cold))
static void
popPageDebug(void *token, AutoreleasePoolPage *page, id *stop)
{
    // The template argument allowDebug passes true
    popPage<true>(token, page, stop);
}
Copy the code

pop

static inline void
pop(void *token)
{
    AutoreleasePoolPage *page;
    id *stop;

    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        // Popping the top-level placeholder pool.
        // Pop up the top-level EMPTY_POOL_PLACEHOLDER pool
        
        / / remove hotPage
        page = hotPage(a);if(! page) {// If hotPage does not exist, there is currently an EMPTY_POOL_PLACEHOLDER, indicating that the pool has not been used
            // Pool was never used. Clear the placeholder.
            // Pool has never been used. Clear placeholders.
            return setHotPage(nil);
        }
        // Pool was used. Pop its contents normally.
        // Pool is already used. The content is displayed normally.
        // Pool pages remain allocated for re-use as usual.
        // Pool pages maintains allocation to use as usual.
        
        // First page
        page = coldPage(a);// Assign the begin of the first page to token
        token = page->begin(a); }else {
        // Token is converted to page
        page = pageForPointer(token);
    }
    
    stop = (id *)token;
    if(*stop ! = POOL_BOUNDARY) {if (stop == page->begin()  &&  !page->parent) {
            // Start of coldest page may correctly not be POOL_BOUNDARY:
            // 1. top-level pool is popped, leaving the cold page in place
            // 2. an object is autoreleased with no pool
        } else {
            // Error. For bincompat purposes this is
            // not fatal in executables built with old SDKs.
            return badPop(token); }}/ / allowDebug to true
    if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
        return popPageDebug(token, page, stop);
    }
    
    // Release object deletes page
    return popPage<false>(token, page, stop);
}
Copy the code

init

static void init(a)
{
    // key tls_dealloc Deletes the page of the release object
    int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                         AutoreleasePoolPage::tls_dealloc);
    ASSERT(r == 0);
}
Copy the code

print

Print the autoRelease object in the current page.

__attribute__((noinline, cold))
void print(a)
{
    // Print hotPage and coldPage
    _objc_inform("[%p] ................ PAGE %s %s %s".this.full()?"(full)" : "".this= =hotPage()?"(hot)" : "".this= =coldPage()?"(cold)" : "");
    check(false);
    
    // Prints the autoRelease object in the current pool
    for (id *p = begin(a); p < next; p++) {if (*p == POOL_BOUNDARY) {
            _objc_inform("[%p] ################ POOL %p", p, p);
        } else {
            _objc_inform("[%p] %#16lx %s", 
                         p, (unsigned long)*p, object_getClassName(*p)); }}}Copy the code

printAll

Prints all autoRelease objects in the autorelease pool.

__attribute__((noinline, cold))
static void printAll(a) // This is a statically non-inline and rarely called function
{
    _objc_inform("# # # # # # # # # # # # # #");
    // Prints the thread where the auto-release pool is located
    _objc_inform("AUTORELEASE POOLS for thread %p".objc_thread_self());

    AutoreleasePoolPage *page;
    
    // Automatically release all objects in the pool
    ptrdiff_t objects = 0;
    // coldePage is the first page
    // Traverse all pages along the child pointer
    for (page = coldPage(a); page; page = page->child) {// Add up the number of AutoRelease objects in each page
        objects += page->next - page->begin(a); }// Prints the number of all autoRelease objects waiting for objc_release in the autorelease pool
    _objc_inform("%llu releases pending.", (unsigned long long)objects);

    if (haveEmptyPoolPlaceholder()) {
        // Prints an empty space pool if it is currently empty
        _objc_inform("[%p] ................ PAGE (placeholder)", 
                     EMPTY_POOL_PLACEHOLDER);
        _objc_inform("[%p] ################ POOL (placeholder)", 
                     EMPTY_POOL_PLACEHOLDER);
    }
    else {
        // Circulates the autoRelease object inside each page
        for (page = coldPage(a); page; page = page->child) { page->print();
        }
    }

    // Print the divider
    _objc_inform("# # # # # # # # # # # # # #");
}
Copy the code

printHiwat

Print high – water.

__attribute__((noinline, cold))
static void printHiwat(a)
{
    // Check and propagate high water mark Ignore high
    // water marks under 256 to suppress noise.
    // Check and propagate high water ignore high water below 256 to suppress noise.
    
    // hotPage
    AutoreleasePoolPage *p = hotPage(a);// COUNT is 4096/8 = 512
    // p->depth is the hotPage depth, the first page is 0,
    // Then depth the page one at a time
    // p-> next-p ->begin() is the number of autoRelease objects stored in the page
    // Mark is probably the page from the first page to the hotPage
    // multiply by 512 and add the number of AutoRelease objects stored in hotPage
    
    uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
    // If mark is greater than p->hiwat and mark is greater than 256
    if (mark > p->hiwat  &&  mark > 256) {
        // Traverse each page along the parent chain and set hiwat on each page to Mark
        for(; p; p = p->parent) {// Can read and write
            p->unprotect(a);// Change hiwat to mark
            p->hiwat = mark;
            
            / / can only be read
            p->protect(a); } _objc_inform("POOL HIGHWATER: new high water mark of %u "
                     "pending releases for thread %p:",
                     mark, objc_thread_self());

        void *stack[128];
        
        // int backtrace(void**,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
        // Function prototype
        // #include <execinfo.h> 
        // int backtrace(void **buffer, int size);
        // This function retrieves the current thread's call stack. The information retrieved will be stored in the buffer.
        // It is an array of Pointers. The size parameter is used to specify buffer
        // How many void * elements can be stored in. The return value of the function is the actual number of void * elements returned.
        // The void * element in buffer is actually the return address retrieved from the stack.
        
        int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
        
        // char** backtrace_symbols(void* const*,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
        // Function prototype
        // char **backtrace_symbols(void *const *buffer, int size);
        // This function converts the information obtained by backtrace into an array of strings,
        // The buffer argument is the stack pointer fetched by backtrace,
        // size is the return value of backtrace.
        // The function returns a pointer to an array of strings containing size char* elements.
        // Each string contains printable information relative to the corresponding element in the buffer,
        // Includes the function name, the function offset address, and the actual return address.
        // Backtrace_symbols can generate strings using malloc memory.
        // It is a one-time malloc, but only a one-time release of the returned secondary pointer
        
        char **sym = backtrace_symbols(stack, count);
        
        for (int i = 0; i < count; i++) {
            _objc_inform("POOL HIGHWATER: %s", sym[i]);
        }
        free(sym); }}Copy the code

#undef POOL_BOUNDARY

#undef POOL_BOUNDARY
Copy the code

At this point, the AuroreleasePoolPage code has been reviewed and all implementation points are clear.

Refer to the link

Reference link :🔗

  • Autorelease behind the black screen
  • GCC extensionattribute ((visibility(“hidden”)))
  • Use __attribute__((visibility (“default”))) in Linux
  • Backtrace function
  • Operating system memory management (Mind map details)
  • The underlying implementation of automatic release pools