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:
- 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.
- The second step is to go directly to the source code, which is generally simple
C++
Code, and thenApple
Encapsulation 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. - 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 1
Automatically release the pool version number only whenABI
Compatibility changes when it is broken.#define PROTECT_AUTORELEASEPOOL 0
Set 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_t
的check()
和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 inDebug
State 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))
- Used to tell the compiler to keep a static function or variable in the object file, even if it is not referenced.
- Marked as
attribute__((used))
The function is marked in the object file to avoidThe linkerDelete unused sections. - A static variableYou can also label it
used
, the method is to useattribute((used))
. - use
used
Field, even without any references, inRelease
It 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:
PROT_READ
: indicates that the contents of the memory segment are readable.PROT_WRITE
: indicates that the contents of the memory segment are writable.PROT_EXEC
: indicates that the contents of the memory segment are executable.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 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