OC原理-autoreleasepool

152 阅读3分钟

引入为题

将项目设置成MRC

#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[[Person alloc] init] autorelease];
    }
    return 0;
}
@implementation Person
-(void)dealloc{
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end

我们发现对象person被添加进自动释放池而且最终被释放了。那么person对象被释放的时机是什么?自动释放池又是如何工作的?

将main.m转化c++文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    	//这里相当于Person *person = [[[Person alloc] init] autorelease];
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
    return 0;
}



struct __AtAutoreleasePool {
  __AtAutoreleasePool() { //构造函数,在生成结构体变量的时候调用
    atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
  ~__AtAutoreleasePool() {//析构函数,在生成结构体变量销毁的时候调用
  	objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  void * atautoreleasepoolobj;
};

我们发现代码中的@autoreleasepool关键字转化成了一个结构体变量,创建结构体变量时,调用结构体的构造函数,结构体变量销毁时,调用结构体的析构函数。

{ 
    __AtAutoreleasePool __autoreleasepool; 
    Person *person = [[[Person alloc] init] autorelease];
}
就会变成
{ 
    //__AtAutoreleasePool __autoreleasepool; 
    //创建一个结构体变量__autoreleasepool,调用构造函数
    atautoreleasepoolobj = objc_autoreleasePoolPush();
    Person *person = [[[Person alloc] init] autorelease];
    
    //结构体变量__autoreleasepool销毁,调用析构函数
    objc_autoreleasePoolPop(atautoreleasepoolobj);
}

我们可以在源码NSObject.mm中找到这两个方法。里面有没有调用了AutoreleasePoolPage类的方法。

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

NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的

AutoreleasePoolPage

class AutoreleasePoolPage : private AutoreleasePoolPageData{
}
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)
    {
    }
};
  • 每个AutoreleasePoolPage对象占用4096字节内存,除了存放其成员变量,生下来的用来存放执行过autorelease的对象的地址。
  • 所有的AutoreleasePoolPage对象通过双向链表连在一起,其成员变量parent和child指针分别指向上一个和下一个AutoreleasePoolPage对象

//获取从哪里开始存放 **执行过autorelease的对象**
id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}
//获取AutoreleasePoolPage对象结束地址
id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

压栈

  • 调用push假如page对象不存在,会先创建一个page对象,然后将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址。
  • 然后加当一个对象调用了autorelease,把这个对象的地址存放到page中。当当前page存放满后,在创建一个新的page对象,把当前page的chlid指针指向新的page对象,新的page对象的parent指针指向上一个当前page对象。
@autoreleasepool { //r1 = push()
    Person *person1 = [[[Person alloc] init] autorelease];
    Person *person2 = [[[Person alloc] init] autorelease];
    @autoreleasepool { //r2 = push()
        Person *person3 = [[[Person alloc] init] autorelease];
    } //pop(r2)
}//pop(r1)

出现嵌套结构page中又是如何存储的呢。

//可以通过以下私有函数来查看自动释放池的情况  
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person1 = [[[Person alloc] init] autorelease];
        Person *person2 = [[[Person alloc] init] autorelease];
        _objc_autoreleasePoolPrint();
        @autoreleasepool {
            Person *person3 = [[[Person alloc] init] autorelease];
            _objc_autoreleasePoolPrint();
        }
    }
    return 0;
}

出栈

调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY


- (void)viewDidLoad {
    [super viewDidLoad]; 
    @autoreleasepool {
        Person *person = [[[Person alloc] init] autorelease];
    }//person对象这里被释放
}
//这种情况呢
- (void)viewDidLoad {
    [super viewDidLoad]; 
    Person *person = [[[Person alloc] init] autorelease];
}
  • 这里用到了runloop,iOS主线程的Runloop中注册了两个Observer
    • 第一个监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
    • 第二个监听了kCFRunLoopBeforeWaiting和kCFRunLoopBeforeExit事件
      • 监听到kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()

      • 监听到kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

这样就会runloop休眠之前会清空一次自动释放池,runloop退出时也会清空一次自动释放池。