博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
autoreleasepool底层探索
阅读量:5134 次
发布时间:2019-06-13

本文共 10109 字,大约阅读时间需要 33 分钟。

#自动释放池初探 修改main.m里的代码如下:

int main(int argc, const char * argv[]) {    @autoreleasepool {    }    return 0;}复制代码

我们通过clang命令来看一下main.m的c++实现:

clang -rewrite-objc main.m -o main.cpp复制代码

打开.cpp查看,可以看到,被编译成这样,产生了一个__AtAutoreleasePool类型的临时对象:

int main(int argc, const char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;     }    return 0;}复制代码
struct __AtAutoreleasePool {  __AtAutoreleasePool() {   atautoreleasepoolobj = objc_autoreleasePoolPush(); }  ~__AtAutoreleasePool() {   objc_autoreleasePoolPop(atautoreleasepoolobj);}   void * atautoreleasepoolobj;};复制代码

__AtAutoreleasePool里面有两个函数, __AtAutoreleasePool() 和 ~__AtAutoreleasePool() ,c++中 结构体名的函数表示初始化时调用, ~+结构体名的函数会在析构时调用。 来看一个例子:自定义一个结构体:(为了支持c++编译,需要把main.m改为main.mm),然后来声明一个对象,查看log:

所以我们可以得出结论,自动释放池在初始化的时候执行了objc_autoreleasePoolPush函数,在析构的时候执行了objc_autoreleasePoolPop函数。

void * _objc_autoreleasePoolPush(void){    return objc_autoreleasePoolPush();}void * objc_autoreleasePoolPush(void){    return AutoreleasePoolPage::push();}void objc_autoreleasePoolPop(void *ctxt){    AutoreleasePoolPage::pop(ctxt);}void _objc_autoreleasePoolPop(void *ctxt){    objc_autoreleasePoolPop(ctxt);}复制代码

AutoreleasePoolPage::push():::是一个属性函数,说明AutoreleasePoolPage是一个结构体对象,来查看它的数据结构(为了便于理解,只列出其属性和我们需要查看的函数):

/**    struct magic_t {        #   define EMPTY_POOL_PLACEHOLDER ((id*)1)      //空占位符        #   define POOL_BOUNDARY nil        //边界符        static const uint32_t M0 = 0xA1A1A1A1;        #define M1 "AUTORELEASE!"        static const size_t M1_len = 12;        uint32_t m[4];     */    magic_t const magic;      // 4个uint32_t 占16位    id *next;    //8位    pthread_t const thread;    //8位    AutoreleasePoolPage * const parent;    //8位    指向下一个page    AutoreleasePoolPage *child;    //8位    指向上一个page    uint32_t const depth;    //4位    uint32_t hiwat;    //4位    static void * operator new(size_t size) {        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);    }复制代码

我们来算一下这些属性占了多少位:入上图加起来可以得到一个数字:56。 接着往下看new函数,创建时的SIZE根据宏定义我们一步步寻找:得到的数字为:4096,即一个AutoreleasePoolPage所能容纳的大小

static size_t const SIZE = #if PROTECT_AUTORELEASEPOOL        PAGE_MAX_SIZE;  // must be multiple of vm page size#else        PAGE_MAX_SIZE;  // size and alignment, power of 2#endif#define PAGE_MAX_SIZE           PAGE_SIZE#define	PAGE_SIZE		I386_PGBYTES#define I386_PGBYTES		4096		/* bytes per 80386 page */复制代码

###什么时候使用autoreleasepool ?

  1. 大量的临时变量;
  2. 某些非UI操作;
  3. 自己创建的辅助线程等等。

要进行自动释放,第一步肯定是要先存进去,这样才能进行释放,每一个page可以容纳4096个字节,而自己本身占了56个,所以每一个page可以容纳的最多对象数为:(4096-56)/ 8 = 505个。 继续进行代码测试,并通过void_objc_autoreleasePoolPrint来打印出autoreleasepool的信息(void_objc_autoreleasePoolPrint为系统函数,这里把它挪出来方便调试):

extern void_objc_autoreleasePoolPrint(void);int main(int argc, const char * argv[]) {    @autoreleasepool {        for (int i = 0; i < 505; i++) {            NSObject * obj = [[NSObject alloc] init];            void_objc_autoreleasePoolPrint();        }    }    return 0;}objc[42414]: AUTORELEASE POOLS for thread 0x1000bd5c0objc[42414]: 506 releases pending.objc[42414]: [0x103003000]  ................  PAGE  (hot) (cold)objc[42414]: [0x103003038]  ################  POOL 0x103003038objc[42414]: [0x103003040]       0x10061d340  NSObjectobjc[42414]: [0x103003048]       0x100604c70  NSObjectobjc[42414]: [0x103003050]       0x100606ba0  NSObjectobjc[42414]: [0x103003058]       0x1006079b0  NSObjectobjc[42414]: [0x103003060]       0x10061d150  NSObjectobjc[42414]: [0x103003068]       0x100614f70  NSObjectobjc[42414]: [0x103003070]       0x100615280  NSObjectobjc[42414]: [0x103003078]       0x100611700  NSObjectobjc[42414]: [0x103003080]       0x10060ff80  NSObjectobjc[42414]: [0x103003088]       0x10060cc30  NSObjectobjc[42414]: [0x103003090]       0x100609e30  NSObjectobjc[42414]: [0x103003098]       0x100609fd0  NSObjectobjc[42414]: [0x1030030a0]       0x100605a00  NSObjectobjc[42414]: [0x1030030a8]       0x100607810  NSObject...objc[42439]: [0x103002fe0]       0x10061e840  NSObjectobjc[42439]: [0x103002fe8]       0x10061e850  NSObjectobjc[42439]: [0x103002ff0]       0x10061e860  NSObjectobjc[42439]: [0x103002ff8]       0x10061e870  NSObjectobjc[42439]: [0x101005000]  ................  PAGE  (hot) objc[42439]: [0x101005038]       0x10061e880  NSObjectobjc[42439]: ##############Program ended with exit code: 0复制代码

发现log信息里有两个page,并且第一个page的状态是从hot->cold ,第二个page的状态是hot,说明此时有两个page,第一个页面已经满了并置为cold状态,第二个page当前是激活状态(hot)。 但是这个为什么跟刚才算出来的不一样呢?505个应该是正好一页就够用了,为什么有个第二页呢?这是因为在添加第一个对象的时候有一个边界符:POOL_BOUNDARY,边界符的作用就是做一个标记,比如删除的时候,删到什么时候为止呢?到了边界符就停止删除操作。

#autoreleasepool源码解析 autoreleasepool是由若干个AutoreleasePoolPage以双向链表的形式组合而成的栈结构(分别对应结构中的parent指针和child指针) ##objc_autoreleasePoolPush 接着**AutoreleasePoolPage::push()**进行源码分析,跳入push:

static inline void *push()     {        id *dest;        if (DebugPoolAllocation) {            //区别DEBUG模式            dest = autoreleaseNewPage(POOL_BOUNDARY);        } else {            //直接从边界符开始进行压栈操作             添加一个哨兵对象到自动释放池的链表栈中            dest = autoreleaseFast(POOL_BOUNDARY);        }        return dest;    }复制代码

###autoreleaseFast

static inline id *autoreleaseFast(id obj)    {        AutoreleasePoolPage *page = hotPage();    //取到当前处于hot状态的page        if (page && !page->full()) {    //取到了并且没满 直接add进去            return page->add(obj);        } else if (page) {      //取到了 但是满了             return autoreleaseFullPage(obj, page);        } else {    //没有取到            return autoreleaseNoPage(obj);        }    }复制代码

autoreleaseFullPage

static __attribute__((noinline))    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)    {        //一个while循环,递归查找page->child,如果找到就把它设置成**hot**状态,没找到就**new**一个,最终通过page->add(obj)添加obj        do {            if (page->child) page = page->child;            else page = new AutoreleasePoolPage(page);        } while (page->full());        setHotPage(page);        return page->add(obj);    }    AutoreleasePoolPage(AutoreleasePoolPage *newParent)         : magic(), next(begin()), thread(pthread_self()),          parent(newParent), child(nil),           depth(parent ? 1+parent->depth : 0),           hiwat(parent ? parent->hiwat : 0)    {         if (parent) {            parent->check();            assert(!parent->child);            parent->unprotect();            parent->child = this;    //让它的parent的child指向自己,保持双向指向            parent->protect();        }        protect();    }  //最终执行page->add(obj);    id *add(id obj)    {        unprotect();        id *ret = next;  // 获取当前的next指针        *next++ = obj;  //让next指向obj   并且next++(先引用,再增加)        protect();        return ret;    }复制代码

autoreleaseNoPage

id *autoreleaseNoPage(id obj)    {        bool pushExtraBoundary = false;        if (haveEmptyPoolPlaceholder()) {            //判断是不是已经有了占位符            pushExtraBoundary = true;        }        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {            //出现异常            objc_autoreleaseNoPool(obj);            return nil;        }        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {            //设置占位符            return setEmptyPoolPlaceholder();        }        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);        setHotPage(page);                if (pushExtraBoundary) {            page->add(POOL_BOUNDARY);    //开始的时候先add边界符POOL_BOUNDARY        }        return page->add(obj);      //最终执行page->add    }复制代码

总结一下:

  1. 没有page的时候,创建第一个page,并且add一个边界符;
  2. 开始正常add对象;
  3. page add满了后,创建一个新的page,并设置为hot,此时parent变为cold ##objc_autoreleasePoolPop
static inline void pop(void *token)     // token指针指向栈顶的地址    {        AutoreleasePoolPage *page;        id *stop;        page = pageForPointer(token);    // 通过栈顶的地址找到对应的page        page->releaseUntil(stop);    // 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象,这一步会删除掉每个page里的每个对象        // 删除所有空page        if (DebugPoolAllocation  &&  page->empty()) {            AutoreleasePoolPage *parent = page->parent;            page->kill();      //杀掉自己            setHotPage(parent);    //将parent置为hot        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {          //page已经没有parent了,直接杀死            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();            }        }    }       void releaseUntil(id *stop)     {        while (this->next != stop) {            AutoreleasePoolPage *page = hotPage();            while (page->empty()) {                page = page->parent;    //page里的对象已经全部删了,将page指向parent,并置为hot                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);   //向page中每一个对象发送release            }        }        setHotPage(this);    }//page kill操作    void kill()     {        AutoreleasePoolPage *page = this;        while (page->child) page = page->child;    //递归找到最下面一层的page        AutoreleasePoolPage *deathptr;        do {            deathptr = page;            page = page->parent;    //指向parent            if (page) {                page->unprotect();                page->child = nil;    //删掉自己                page->protect();            }            delete deathptr;        } while (deathptr != this);    }复制代码

总结一下:

  1. 删除page里的每一个对象,然后将page指向parent并置为hot,进行递归
  2. 删除所有空的page

所以,出了autoreleasepool的作用域空间,就会释放掉所有的对象。

#autoreleasepool与runloop 苹果在主线程 RunLoop 里注册了两个 Observer: 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop), BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池; Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

转载于:https://juejin.im/post/5d22f34b6fb9a07ef90cbd25

你可能感兴趣的文章
Sql Server 中由数字转换为指定长度的字符串
查看>>
tmux的简单快捷键
查看>>
[Swift]LeetCode922.按奇偶排序数组 II | Sort Array By Parity II
查看>>
php match_model的简单使用
查看>>
SIP服务器性能测试工具SIPp使用指导(转)
查看>>
Vue_(组件通讯)子组件向父组件传值
查看>>
STM32单片机使用注意事项
查看>>
032. asp.netWeb用户控件之一初识用户控件并为其自定义属性
查看>>
移动开发平台-应用之星app制作教程
查看>>
leetcode 459. 重复的子字符串(Repeated Substring Pattern)
查看>>
springboot No Identifier specified for entity的解决办法
查看>>
浅谈 unix, linux, ios, android 区别和联系
查看>>
51nod 1428 活动安排问题 (贪心+优先队列)
查看>>
latex for wordpress(一)
查看>>
如何在maven工程中加载oracle驱动
查看>>
Flask 系列之 SQLAlchemy
查看>>
aboutMe
查看>>
【Debug】IAR在线调试时报错,Warning: Stack pointer is setup to incorrect alignmentStack,芯片使用STM32F103ZET6...
查看>>
一句话说清分布式锁,进程锁,线程锁
查看>>
FastDFS使用
查看>>