探究Autorelease本质

一、先创建一个Dog类,重写dealloc方法,看看Dog类的实例什么时候释放。工程改为MRC 工程:

image.png

image.png

发现实例并没有释放,在MRC 下需要添加 autorelease 或则 release

image.png

二、c++ 重写 main.m
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Dog *g = ((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Dog"), sel_registerName("alloc")), sel_registerName("init"));
    }
    return 0;
}
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {//构造函数,在创建结构体时调用
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
  ~__AtAutoreleasePool() {//析构函数,在销毁结构体时调用
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  void * atautoreleasepoolobj;
};

1.__AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); }:构造函数,在创建结构体时调用
2.~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); }:析构函数,在销毁结构体时调用

3.所以上面代码可以改为:
伪代码

{
    __AtAutoreleasePool __autoreleasepool;
    atautoreleasepoolobj = objc_autoreleasePoolPush();
    Dog *g = [[[Dog alloc] init] autorelease];
    objc_autoreleasePoolPop(atautoreleasepoolobj);
}

__autoreleasepool 是一个局部变量,大括号结束就会调用objc_autoreleasePoolPop,将autorelease 对象释放。

三、看源码实现

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

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
class AutoreleasePoolPage 
{
    PAGE_MAX_SIZE;
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}

以上源码只保留重要部分。


704C76E6-6322-4DD3-9751-FA296AB96C79.png

1.AutoreleasePoolPage 本质是这么一个结构,大小是4096字节。(PAGE_MAX_SIZE:4096,源码得到)。
2.前7个变量都是8字节,剩下的4040字节存储着autorelease对象地址
3.AutoreleasePoolPage * const parent; AutoreleasePoolPage *child;:由此可知当4040字节无法放下autorelease对象地址时,会扩展一个AutoreleasePoolPage,且该数据结构是,双向链表

54B8E5AE-2A59-4604-8CC1-20FE67033480.png

4.next 指针指向当前autorelease对象地址位置,每push 一个 对象,next 就会++。

id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

5.POOL_BOUNDARY 边界:# define POOL_BOUNDARY nil
比如:

@ autoreleasepool{
      obj2
    @ autoreleasepool{
        obj1
    }
}

每次添加一个@ autoreleasepool,就相当于push一次POOL_BOUNDARY,出大括号,就pop到最近的POOL_BOUNDARY,也就是obj1 被pop 了。接着obj2 离开大括号,也就是pop到最近的POOL_BOUNDARY操作。
嵌套的AutoreleasePool:pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已。

    static inline void *push() 
    {
        id *dest;
        if (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;
    }

每次push 对象的时候会先pushPOOL_BOUNDARY 边界。

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

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

begin:7个成员变量之后的开始位置
end:结束位置

7.releaseUntil(begin()); :release 直到 begin

hotPage():当前AutoreleasePoolPage
coldPage:非当前AutoreleasePoolPage

四、runloop 、 autorelease、子线程

1.主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理。

2.子线程默认不开启runloop,当产生autorelease对象时候,会将对象添加到AutoreleasePoolPage中,也就是不手动创建Autoreleasepool也能正确释放对象,线程销毁时release对象

3.自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。
但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。

4.AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程),没开一个线程,会有与之对应的AutoreleasePool

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

    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 __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        assert(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }

        // We are pushing an object or a non-placeholder'd pool.

        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);
    }

Autoreleasepool 销毁时机也就是autorelease 对象销毁时机

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

推荐阅读更多精彩内容