Objc open source code

Autoreleasepool introduction

AutoreleasePool: Automatic release pool is an automatic release mechanism provided by OC. It has the feature of deferred release. When we create an object and add it to the automatic release pool, it will not be released immediately, but will wait until a runloop ends or the scope is out of {} or beyond [pool release].

(2) the alloc/init/new/copy/mutableCopy return objects are not autorelease objects

void createString(void) {
    
    NSString *taggedPointerStr = [[NSString alloc] initWithFormat:@"Hello"];// Create a TaggedPointer
    NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"];// Create a regular object
    NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"];// Create an Autorelease object
    
    weak_TaggedPointerStr = taggedPointerStr;
    weak_allocString = string;
    weak_StringAutorelease = stringAutorelease;
    
    NSLog(@"------in the createString()------");
    NSLog(@ "% @", weak_TaggedPointerStr);
    NSLog(@ "% @", weak_allocString);
    NSLog(@"%@\n\n", weak_StringAutorelease);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.navigationItem.title = @"AotoReasePool";
    self.view.backgroundColor = [UIColor whiteColor];
    
    @autoreleasepool {
        createString();
        NSLog(@"------in the autoreleasepool------");
        NSLog(@ "% @", weak_TaggedPointerStr);
        NSLog(@ "% @", weak_allocString);
        NSLog(@"%@\n\n", weak_StringAutorelease);
    }
    NSLog(@"------in the main()------");
    NSLog(@ "% @", weak_TaggedPointerStr);
    NSLog(@ "% @", weak_allocString);
    NSLog(@ "% @", weak_StringAutorelease);
}
Copy the code

Output:

 ------in the createString()------
 Hello
 Hello, World!
 Hello, World! Autorelease

 ------in the autoreleasepool------
 Hello
 (null)
 Hello, World! Autorelease

 ------in the main()------
 Hello
 (null)
 (null)
Copy the code

From the output above, we can see that the Hello from alloc always exists, while the Hello from alloc, World! The created object is released when it is out of the current scope. And if you have stringWithFormat creating an Autorelease object it will delay the release. Why is that?

1. Alloc/init/new/copy/mutableCopy objects created by not autorelease object, only in the current scope. 2. If a string contains less than or equal to eight characters, the system optimizes it into Tagged Pointer. Tagged Pointer stores data directly in the Pointer itself by setting a special flag on its last bit. Tagged Pointer is not a real object. When using it, be careful not to access its ISA variable directly.Copy the code

(3) AutoreleasePoolPage structure:

Before we learn how to do this, what is Autorelease EpoolPage

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
	magic_t const magic;
	__unsafe_unretained id *next;
	pthread_t const thread;
	AutoreleasePoolPage * const parent;
	AutoreleasePoolPage *child;
	uint32_t const depth;
	uint32_t hiwat;

	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

AutoreleasePoolPage is an AutoreleasePoolPage structure. Magic: verifies that the AutoreleasePoolPage structure is complete. Next: points to the top of the stack. Parent: indicates the parent node. Child: indicates the child node. Depth: indicates the depth of the list, that is, the number of nodes in the list.

A thread’s autoreleasepool is a pointer stack, and we add a POOL_BOUNDARY marker before each push and then add objects. When AutoReleasepool exits the stack, every object that is pushed after the sentinel object is released. The stack consists of a bidirectional linked list with pages as nodes, which increase or decrease as needed. The autoreleasepool thread stores references to the latest page. Draw a picture to understand:

(4) AutoreleasePool implementation:

  • How to add objects
/* @autoreleasepool */ __AtAutoreleasePool __autoreleasepool; 
Copy the code

@autoreleasepool is used to declare a local variable of type __AtAutoreleasePool.

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

This is a syntactic sugar where the constructor is called at the start of a local variable and the destructor is called at the end of the scope. Let’s take a look at the objC source code for these two functions

void *
_objc_autoreleasePoolPush(void)
{
    return objc_autoreleasePoolPush();
}
Copy the code
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
Copy the code
static inline void *push(a) 
{
    id *dest;
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.
        // A new page is created at the start of each release pool
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        // Quickly add sentinel object nil
        dest = autoreleaseFast(POOL_BOUNDARY);// The sentinel object
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
Copy the code

AutoreleaseFast (POOL_BOUNDARY), POOL_BOUNDARY We call it the sentinel object, and if you look at its macro, it’s a nil.

#   define POOL_BOUNDARY nil
Copy the code
static inline id *autoreleaseFast(id obj)
{
    // Lists have Spaces
    AutoreleasePoolPage *page = hotPage();// Go back to the latest page
    if(page && ! page->full()) {//1. Add obj to obj and add page to obj
        return page->add(obj);
    } else if (page) {Create a new page, add obj, child pointer to the newly created page
        return autoreleaseFullPage(obj, page);
    } else {// No page, create a page and add obj
        returnautoreleaseNoPage(obj); }}Copy the code

How do I add objC to a linked list by calling add(obj)

id *add(id obj)// Insert objc like the current list
{ ASSERT(! full()); unprotect();// Remove the protection
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;// push obj to the top of the stack and relocate it
    protect();
    return ret;
}
Copy the code

Create a sentinel object POOL_BOUNDARY. After adding the object, point the next pointer to the last position.

  • How are objects freed
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code
static inline void
pop(void *token)// The token pointer points to the address at the top of the stack
{
    AutoreleasePoolPage *page;
    id *stop;
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
    } else {
        page = pageForPointer(token);// Find the corresponding page from the address at the top of the stack
    }
    // Some code is omitted
    return popPage<false>(token, page, stop);
}
Copy the code

Get the current page and pop

static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    if (allowDebug && PrintPoolHiwat) printHiwat();// Record the highest watermark mark

    page->releaseUntil(stop);// Operate from the top of the stack and send a release message to the objects in the stack until the first sentinel object is encountered

    // memory: delete empty children
     // Delete the empty node
    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if(allowDebug && DebugMissingPools && page->empty() && ! page->parent) {// special case: delete everything for pop(top)
        // when debugging missing autorelease pools
        page->kill();
        setHotPage(nil);
    } else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if(page->child->child) { page->child->child->kill(); }}}Copy the code

page->releaseUntil(stop)

void releaseUntil(id *stop) 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    
    while (this->next ! = stop) {// Restart from hotPage() every time, in case -release 
        // autoreleased more objects
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if(obj ! = POOL_BOUNDARY) { objc_release(obj);// Exit if it is not a sentinel object
        }
    }

    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        ASSERT(page->empty());
    }
#endif
}
Copy the code
static inline id autorelease(id obj)
{ ASSERT(obj); ASSERT(! obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj);//ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);return obj;
}
Copy the code

As with push, the key code for autoRelease is to call autoreleaseFast to add an object to the auto-release list stack, but push pushes a sentinel object, The autoRelease function pushes the object that needs to be added to the AutoReleasepool.

(v) Usage scenarios

Object as a function return value
+ (instancetype)object{
    return [[MMPooLPerson alloc] init];
}
Copy the code

When an object as a function return value, because must follow the thought, who apply for release so should be returned before release, but if before returning to release, can cause wild pointer errors, but if you don’t release, it violates the principle of who apply for release, so you can use autorelease delay release characteristics, Make it an autorelease before returning, add it to the autorelease pool, make sure it can be returned, and the system will release it for us after a runloop.

2. Create a large number of objects temporarily

Always put the auto-release pool inside the for loop. If you do this outside, you will run out of memory because too many objects will not be released in time, and the program will exit unexpectedly

for (int i = 0; i<10000; i++) {
    @autoreleasepool {
        UIImageView *imegeV = [[UIImageView alloc]init];
        imegeV.image = [UIImage imageNamed:@"efef"];
        [self.view addSubview:imegeV]; }}Copy the code
Create a new thread
4. Tasks that run in the background for a long time

conclusion

@autoreleasepool

Autoreleasepool {} implicitly creates an autoreleasepool for each runloop, and all autoreleasepool autoreleasepool stacks. At the end of each runloop, the autoreleasePool at the top of the stack is destroyed and each object in it is released once (strictly speaking, you autorelease the object as many times as you do it, not necessarily once).

The system method that will help us automatically write to the auto release pool
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// This is surrounded by a local @autoreleasepool
}];
Copy the code
Automatic release of pool multi-layer nesting

Nested AutoReleasepool: An AutoRelease object is added to the nearest autoreleasepool. Multiple tiers of pools have multiple sentinels.

@autoreleasepool {// P1 is placed in the automatic release pool
    Person *p1 = [[Person alloc]init];
    @autoreleasepool {// P2 is placed in the automatic release poolPerson *p2 = [[Person alloc]init]; }}Copy the code
NSThread, NSRunLoop, and NSAutoreleasePool

1. Each thread, including the main thread, has its own NSRunLoop object, which is automatically created when needed. 2. Each Autoreleasepool corresponds to a thread. A thread can have multiple Autoreleasepool 3. Each thread maintains its own AutoReleasepool. The system automatically creates an AutoReleasepool before each event loop of the NSRunLoop object starts and drains at the end of the event loop