iOS-- autoreleasePool(自动释放池)

手动目录

  • 初探autoreleasePool结构
  • AutoreleasePoolPage 结构
  • 每个Page能装多少个对象
  • push & pop
  • 面试题

初探autoreleasePool结构

用clang的方式探索autoreleasePool

int main(int argc, char * argv[]){
    @autoreleasepool {
        
    }
    return 1;
}

用clang命令:clang -rewrite-objc main.m 之后得到.cpp文件

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

    }
    return 1;
}

关键就在{ __AtAutoreleasePool __autoreleasepool; } ,在.cpp 文件中 找到其结构

// 是一个结构体
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}      // 构造函数
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}         // 析构函数
  void * atautoreleasepoolobj;
};

在这个结构体中,它调用了一个objc_autoreleasePoolPush方法 ,那么它到底干了什么,我们在objc源码中去看

objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();          // 压栈
}

class AutoreleasePoolPage : private AutoreleasePoolPageData

AutoreleasePoolPage 结构

找到这里的时候,我们先来看看AutoreleasePoolPage的说明

   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. 

说明里面提到几点:
1、stack 先进后出
2、pool boundary 边界(也有叫哨兵的),它是一个对象,可以理解为标记。
3、释放池里存的 要么是边界,要么是需要释放的对象。
4、是一个双向链表结构

继续跟踪AutoreleasePoolPage的父类AutoreleasePoolPageData

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic;                        // 检查校验完整性的变量                   16位
    __unsafe_unretained id *next;               // 指向新加入的autorelease对象下一个节点   8
    pthread_t const thread;                     // 当前所在的线程                        8
    AutoreleasePoolPage * const parent;         // 父节点 指向前一个page                  8
    AutoreleasePoolPage *child;                 // 子节点 指向下一个page                 8
    uint32_t const depth;                       // 链表的深度,节点个数                   4
    uint32_t hiwat;                             // 数据容纳的一个上限                     4

    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)
    {
    }
};

struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1;              // 全局静态 在常量区 .data段  不占这里的位数
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;                    // 全局静态 在常量区 .data段  不占这里的位数
    uint32_t m[4];                                      // 4 * 4 = 16   这才是为什么这个结构体16位
    ......
};

相关属性说明

  • magic 检查校验完整性的变量
  • next 指向新加入的autorelease对象下一个位置
  • thread page当前所在的线程
  • parent 父节点 指向前一个page
  • child 子节点 指向下一个page
  • depth 链表的深度,节点个数
  • hiwat 数据容纳的一个上限

链表的结构大概为:


AutoreleasePoolPage

每个Page能装多少个对象

我们通过源码来找:

id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

begin 从 sizeof(this))开始 (上面计算了它的大小 56) 到 SIZE结束
SIZE = PAGE_MIN_SIZE = PAGE_SIZE = 4096;
那么能存的对象数量是:
(4096 - 56)/8 = 505*

打印来验证
在可运行的objc源码中, 修改setting -> automatic reference counting = NO

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (NSInteger i = 0; i < 5; i ++) {
            NSObject *obj = [[NSObject alloc] autorelease];
        }
        _objc_autoreleasePoolPrint();
        return 0;
    }
}

// 打印结果
1objc[65796]: ##############
objc[65796]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[65796]: 6 releases pending.                                      // 这里说了  添加了6个
objc[65796]: [0x10181d000]  ................  PAGE  (hot) (cold)
objc[65796]: [0x10181d038]  ################  POOL 0x10181d038
objc[65796]: [0x10181d040]       0x10103d480  NSObject
....
objc[65796]: ##############

// 上面的循环换成 1 + 504 
1objc[66085]: ##############
objc[66085]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[66085]: 506 releases pending.                                                // 这里说了 506
objc[66085]: [0x101809000]  ................  PAGE (full)  (cold)      // 注意这里:  page  full(满了)
objc[66085]: [0x101809038]  ################  POOL 0x101809038
objc[66085]: [0x101809040]       0x10104af30  NSObject
....
objc[66085]: [0x101809ff8]       0x10104f6e0  NSObject
objc[66085]: [0x10180b000]  ................  PAGE  (hot)               // 这里也换了一页
objc[66085]: [0x10180b038]       0x10104f6f0  NSObject
objc[66085]: ##############
KCObjcTest was compiled with optimization - stepping may behave oddly; variables may not be available.

通过更多的打印,我们发现

每页可以添加505个对象
但是第一页实际只加进去504,因为第一页有一个边界对象
后面的每页可以实际加入505个对象

push & pop

先看源码 如何将对象加入进去
在看源码之前先了解hotPage是当前页 对看源码有帮助

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

// push
static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

//autoreleaseFast
static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

后面的 add()  、autoreleaseFullPage()、autoreleaseNoPage()可以继续往下跟。代码太长,就不贴出来了

pop

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

pop(void *token) {}

popPage()

// 具体代码就不贴了。

压栈过程 (push)
1、如果有分页,并且这个页还没有满,直接add
2、如果有分页,并且满了,创建一张新页便加入对象。
3、如果还没有页,创建一个page 并加入边界对象和要加入的对象。

出栈(pop)
1、通过while循环,从后往前找到所有page的对象,并进行release操作
2、删除所有的空表

autoreleasePool 嵌套

嵌套使用autoreleasePool的情况下,会发生什么?
我们用代码来打印

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] autorelease];
        @autoreleasepool {
            NSObject *obj = [[NSObject alloc] autorelease];
            @autoreleasepool {
                NSObject *obj = [[NSObject alloc] autorelease];
                _objc_autoreleasePoolPrint();
            }
        }
    }
    return 0;
}
// 打印结果
objc[70971]: ##############
objc[70971]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[70971]: 6 releases pending.
objc[70971]: [0x10080c000]  ................  PAGE  (hot) (cold)
objc[70971]: [0x10080c038]  ################  POOL 0x10080c038
objc[70971]: [0x10080c040]       0x100683ca0  NSObject
objc[70971]: [0x10080c048]  ################  POOL 0x10080c048
objc[70971]: [0x10080c050]       0x100683e40  NSObject
objc[70971]: [0x10080c058]  ################  POOL 0x10080c058
objc[70971]: [0x10080c060]       0x100683b90  NSObject
objc[70971]: ##############

在看另外一种打印

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *objc = [[NSObject alloc] autorelease];
        NSLog(@"--------objc = %@",objc);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{        // 子线程有一个隐性的autoreleasepool
            @autoreleasepool {
                sleep(1);
                NSObject *obj = [[NSObject alloc] autorelease];
                NSLog(@"+++++++obj = %@",obj);
                _objc_autoreleasePoolPrint();
            }
        });
        _objc_autoreleasePoolPrint();
    }
    sleep(2);          // 防止子线程没有执行完
    return 0;
}

// 打印结果
Test[74472:1192459] --------objc = <NSObject: 0x10290d0b0>
objc[74472]: ##############                                     // 第一个autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x1000d5dc0                  // 依赖的线程不同
objc[74472]: 2 releases pending.
objc[74472]: [0x10300a000]  ................  PAGE  (hot) (cold)
objc[74472]: [0x10300a038]  ################  POOL 0x10300a038
objc[74472]: [0x10300a040]       0x10290d0b0  NSObject
objc[74472]: ##############
KCObjcTest[74472:1192759] +++++++obj = <NSObject: 0x102804ac0>
objc[74472]: ##############                  // 第二个autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x70000b669000
objc[74472]: 3 releases pending.
objc[74472]: [0x10380c000]  ................  PAGE  (hot) (cold)
objc[74472]: [0x10380c038]  ################  POOL 0x10380c038        // 一个隐性autoreleasepool
objc[74472]: [0x10380c040]  ################  POOL 0x10380c040        //手动添加的autoreleasepool
objc[74472]: [0x10380c048]       0x102804ac0  NSObject
objc[74472]: ##############
  • 1、相同的线程,嵌套autoreleasePool 用的是相同的page,但是每个autoreleasepool 都会添加一个边界对象(哨兵)
  • 2、 不同的线程 使用不同的autoreleasePool

面试题

来源

  • 1、ARC 下什么样的对象由 Autoreleasepool 管理
    当使用alloc/new/copy/mutableCopy开始的方法进行初始化时,会生成并持有对象(也就是不需要pool管理,系统会自动的帮他在合适位置release)

    不是通过alloc等出来的对象 会加入自动释放池 比如id obj = [NSMutableArray array];

  • 2、Autoreleasepool 与 Runloop 的关系
    主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理

  • 3、子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?

    在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335