美文网首页
iOS autorelease 实现原理及释放时机

iOS autorelease 实现原理及释放时机

作者: 孙掌门 | 来源:发表于2020-03-01 16:14 被阅读0次

iOS autorelease 实现原理及释放时机


int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        person *p = [[person alloc] init];
        
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

我们见文件转化为 C++ 文件,xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m ,然后生成一个.cpp,


int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
        person *p = ((person *(*)(id, SEL))(void *)objc_msgSend)((id)((person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("person"), sel_registerName("alloc")), sel_registerName("init"));

    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

这个是我们生成的 main 的源码,可以看到 在开头生成了一个 __AtAutoreleasePool __autoreleasepool;


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


可以看到有构造函数和析构函数,当我们创建结构体的时候,会调用构造函数,当结构体销毁的时候就会调用析构函数。所以但我们调用 __AtAutoreleasePool __autoreleasepool; d的时候,就会调用我们的构造函数,然后其实就是调用 objc_autoreleasePoolPush() 当我们除了作用于,结构体销毁的时候,就会调用析构函数,objc_autoreleasePoolPop(atautoreleasepoolobj); 就会调用这个函数,一个 push 一个 pop.

所以我们的代码就相当于


atautoreleasepoolobj = objc_autoreleasePoolPush();
person *p = [[person alloc] init];
objc_autoreleasePoolPop(atautoreleasepoolobj);


接下来结合源码分析,其实我们的 autoreleasepool ,底层是靠一个叫做AutoreleasePoolPage的数据结构管理的

class AutoreleasePoolPage 
{

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

每个 autoreleasepoolpage 占用4096 个字节,除了用来存放他内部的成员变量,剩下的用来存放autorelease对象的地址,所有的 autoreleasepoolpage是通过双向链表连接起来的,child 会指向下一个autoreleasepoolpage,parent 指向上一个,每个 autoreleasepoolpage对象,里面存放着调用autorelease对象的地址值,存放着所有调用过的.


 @autoreleasepool {
            for (NSInteger i = 0 ; i < 1000; i ++) {
                person *p = [[person alloc] init];
            }
        }

我们在 autorelpool里面调用了一千次,一个指针8个字节,一共8000个,我们的autoreleasepoolpage,在存储万自己的实例变量一些信息过后可能剩余4000个字节来存储对象的地址,那么就需要两个autoreleasepoolpage来存储。当我们调用pop的时候,就找到这1000个对象的地址值,然后释放

当我们调用 push 的时候,会将 POOL_BOUNDARY 压栈,其实内部就是定义值为0,然后返回当前地址,当我们有对象调用 autorelease之后,就会在他的下面去压栈,存储调用autorelease对象的地址了,当我们调用pop的时候,我们就会从后往前,挨个调用release,一直到我们穿进去的那个 POOL_BOUNDARY 的位置,这样就会将所有的autorelease兑现都释放掉。其中next指针指向的是第一个能存放autorelease对象地址的位置,当我们调用一次push的时候,就会插入一个POOL_BOUNDARY,每次 pop 的时候,遇到 POOL_BOUNDARY 就会结束,释放当前 autoreleasepoolpage 存储的所有对象,每次pop的时候,会传入POOL_BOUNDARY的地址,然后从最后一个入栈的autorelease对象开始释放,如果一个page页里面能存储下,那么POOL_BOUNDARY会一直在当前page页叠加,只有当前page存放不下,会新建一个page,然后入栈。

autorelease 什么时机会被释放

runloop 注册了两个监听,我们打印下 mainrunloop


<CFRunLoop 0x600003194300 [0x10db64c30]>{wakeup port = 0x2303, stopped = false, ignoreWakeUps = false, 
    3 : <CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (
    12 : <CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (
    "<CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}",
    "<CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}"
    "<CFRunLoopObserver 0x600003c90460 [0x10db64c30]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}",
    "<CFRunLoopObserver 0x600003c90500 [0x10db64c30]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110bcef54), context = <CFArray 0x6000003f9140 [0x10db64c30]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd7da006038>\n)}}"
2020-03-01 16:03:18.346330+0800 testautoreelease[34048:1997377] persom dealloc



可以看到,runloop 注册了监听,第一个observer监听了,KCFRunloopEntry事件,然后调用 objc_autoreleasePoolPush()

第二个observer 监听了,KCFRunloopBeforeWaiting 事件,然后调用objc_autoreleasePoolPop(),然后调用objc_autoreleasePoolPush()。

当我们runloop退出的时候,会调用一次 pop,和之前的 push 对上。

所以我们的 autorelease 是跟我们的runloop有关的,在我们这次runloop休眠之前会pop释放。

所以如果我们生成的对象是autorelease的形式,那么不会立刻释放,会等档次的runloop结束之后释放,但是我们发现我们在arc情况下,viewdidload 里面初始化一个,然后结束之后就会立刻释放,说明很有可能编译器为什么添加了release,当过了viewdidload作用于之后就会调用release将我们的对象释放。

相关文章

网友评论

      本文标题:iOS autorelease 实现原理及释放时机

      本文链接:https://www.haomeiwen.com/subject/vuefkhtx.html