oc篇-深入理解@autoreleasepool

一伦籍、@autoreleasepool到底是干什么的馁蒂?

使用clang -rewrite-objc main.m

@autoreleasepool {
}

翻譯成C++文件可知

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

全局搜索可以得到

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

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

看代碼可知其實是個結構體绘证,上面的代碼可以寫成

atautoreleasepoolobj = objc_autoreleasePoolPush()
...
objc_autoreleasePoolPop(atautoreleasepoolobj)

二、那objc_autoreleasePoolPushobjc_autoreleasePoolPop又是什么呢?

查看runtime的objc4-646目錄下的NSObject.mm源代碼可以知道具體的實現(xiàn)

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

  • magic用來校驗AutoreleasePoolPage結構是否完整;
  • next指向第一個可用的地址寓涨;
  • thread指向當前的線程;
  • parent指向父類
  • child指向子類
id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }    
id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

這兩個方法快速獲得可用的地址范圍

每一個autoreleasepool都是由一個或多個AutoreleasePoolPage的雙向鏈表組成的

    static size_t const SIZE       PAGE_MAX_SIZE;  // size and alignment, power of 2
    static size_t const COUNT = SIZE / sizeof(id);

展開就是

#define I386_PGBYTES        4096        /* bytes per 80386 page */
#define PAGE_MAX_SIZE           PAGE_SIZE

所以:AutoreleasePoolPage的大小都是一樣的4096

關于AutoreleasePoolPage的數(shù)據(jù)結構借用一下Draveness的圖

1975281-366d56087b4600cf.png

大致搞清楚了autoreleasepool的結構我們再下面從autoreleasepool的創(chuàng)建和對象如何加到autoreleasepool來具體說說中間都發(fā)生了什么

三氯檐、autoreleasepool的創(chuàng)建

當我們使用@autoreleasepool { }的時候戒良,由上面知道實際是先調用了objc_autoreleasePoolPush方法,我們先看看objc_autoreleasePoolPush的實現(xiàn)

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

展開其實就是

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

這個方法就是AutoreleasePoolPage具體初始化的地方冠摄,看實現(xiàn)知道有三種情況

  • hotPage存在并且容量沒有滿糯崎,直接添加對象
  • hotPage存在但是容量已經(jīng)滿了,調用autoreleaseFullPage方法河泳,初始化一個AutoreleasePoolPage并把page傳入沃呢,并標記為hotPage;
  • hotPage不存在拆挥,調用autoreleaseNoPage創(chuàng)建一個AutoreleasePoolPage薄霜,并標記為hotePage,并且添加一個POOL_SENTINEL(哨兵對象)

上面的autoreleaseFast方法就是創(chuàng)建autoreleasepool的核心實現(xiàn)
另外看到這個關鍵字了吧inline纸兔,其實就是內聯(lián)函數(shù)惰瓜,可以提供執(zhí)行速度,可以看看我之前的文章

總結:每次push會產(chǎn)生一個新的autoreleasepool汉矿,并生成一個POOL_SENTINEL

四崎坊、對象是如何加到autoreleasepool中去的

說完autoreleasepool的創(chuàng)建,接下來說對象是如何加到autoreleasepool中去的洲拇,當對象調用【object autorelease】的方法的時候就會加到autoreleasepool中

+ (id)autorelease {
    return (id)self;
}

// Replaced by ObjectAlloc
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

展開為

inline id 
objc_object::rootAutorelease()
{
    assert(!UseGC);

    if (isTaggedPointer()) return (id)this;
    if (fastAutoreleaseForReturn((id)this)) return (id)this;

    return rootAutorelease2();
}

再展開

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

其實就是AutoreleasePoolPage的autorelease方法

   static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  *dest == obj);
        return obj;
    }

是不是覺得似曾相識奈揍?是的曲尸,和之前的push方法幾乎一樣,就是入?yún)⒉灰粯印?/p>

  • push方法傳的是POOL_SENTINEL
  • autorelease方法是傳的具體需要autoRelease對象

細心的同學肯定發(fā)現(xiàn)了assert(!obj->isTaggedPointer());這個斷言里面的isTaggedPointer了吧男翰,這個是蘋果后面為了提高內存利用率推出來的偽指針另患,要想深入了解可以點擊此處

對比完autoreleasepool的創(chuàng)建和對象如何加到autoreleasepool我們可以得出下面的結論:

  1. 只有push操作會產(chǎn)生POOL_SENTINEL奏篙;
  2. POOL_SENTINEL的個數(shù)就是autoreleasepool的個數(shù)柴淘,實際開發(fā)中會有嵌套使用的情況。

五秘通、說完添加接著來說說移除pop

當執(zhí)行到這個objc_autoreleasePoolPop方法的時候
autoreleasepool會向POOL_SENTINEL地址后面的對象都發(fā)release消息为严,直到第一個POOL_SENTINEL對象截止。

1433231260106282.jpg

我們來理解一下這個圖:

  1. 首先看到有兩個POOL_SENTINEL說明autoreleasepool還嵌套了一個autoreleasepool肺稀,當最新的autoreleasepool執(zhí)行objc_autoreleasePoolPop方法的時候第股,最右邊的AutoreleasePoolPage向里面的對象發(fā)release消息直到中間那個POOL_SENTINEL為止
  2. coldPage方法的定義可知最左邊的那個是coldPage
 static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
                result->fastcheck();
            }
        }
        return result;
    }
1433231268131918.jpg

依次類推。话原。夕吻。

六、實踐

理論說完了繁仁,究竟在什么地方用這個呢涉馅,看蘋果官方文檔

  1. 如果你編寫的程序不是基于 UI 框架的,比如說命令行工具黄虱;
  2. 如果你編寫的循環(huán)中創(chuàng)建了大量的臨時對象稚矿;
  3. 如果你創(chuàng)建了一個輔助線程。

官方提供了一個例子

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
 
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                         encoding:NSUTF8StringEncoding error:&error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

這里特地寫了一個demo測試這個

static const int kStep = 50000;
static const int kIterationCount = 10 * kStep;
//查看app運行內存
- (void)obserMemoryUsage{
    NSNumber *num = nil;
    NSString *str = nil;
    for (int i = 0; i < kIterationCount; i++) {
        @autoreleasepool {
        
           num = [NSNumber numberWithInt:i];
          str = [NSString stringWithFormat:@"打哈薩克的哈克實打實的哈克時間的話大聲疾呼多阿薩德愛仕達按時 "];
            
            //Use num and str...whatever...
            [NSString stringWithFormat:@"%@%@", num, str];
            
            if (i % kStep == 0) {
                double ff  =     getMemoryUsage();
                NSLog(@"%f",ff);
            }
        }
    }
}
double getMemoryUsage(void) {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    
    double memoryUsageInMB = kerr == KERN_SUCCESS ? (info.resident_size / 1024.0 / 1024.0) : 0.0;
    
    return memoryUsageInMB;
}

統(tǒng)一操作了3次我們對比一下內存占用情況

加了 @autoreleasepool {}

控制臺

A0CC8527-0555-4744-A52A-B5D8005BCC01.png

不加 @autoreleasepool {}

788D40F9-EDDD-4728-AA58-69777BBC137C.png

同樣是3次加了 @autoreleasepool {}內存穩(wěn)定在73左右捻浦,而不加 @autoreleasepool {}的會出現(xiàn)暴增的情況晤揣。

@autoreleasepool {}平時不怎么關心,仔細研究起來還是挺有用處的朱灿。

reference:

  1. http://www.reibang.com/p/32265cbb2a26
  2. http://blog.csdn.net/hepburn_/article/details/47018509
  3. https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末昧识,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盗扒,更是在濱河造成了極大的恐慌跪楞,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侣灶,死亡現(xiàn)場離奇詭異习霹,居然都是意外死亡,警方通過查閱死者的電腦和手機炫隶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阎曹,“玉大人伪阶,你說我怎么就攤上這事煞檩。” “怎么了栅贴?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵斟湃,是天一觀的道長。 經(jīng)常有香客問我檐薯,道長凝赛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任坛缕,我火速辦了婚禮墓猎,結果婚禮上,老公的妹妹穿的比我還像新娘赚楚。我一直安慰自己毙沾,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布宠页。 她就那樣靜靜地躺著左胞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪举户。 梳的紋絲不亂的頭發(fā)上烤宙,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音俭嘁,去河邊找鬼躺枕。 笑死,一個胖子當著我的面吹牛兄淫,可吹牛的內容都是我干的屯远。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼捕虽,長吁一口氣:“原來是場噩夢啊……” “哼慨丐!你這毒婦竟也來了?” 一聲冷哼從身側響起泄私,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤房揭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晌端,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捅暴,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年咧纠,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓬痒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡漆羔,死狀恐怖梧奢,靈堂內的尸體忽然破棺而出狱掂,到底是詐尸還是另有隱情,我是刑警寧澤亲轨,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布趋惨,位于F島的核電站,受9級特大地震影響惦蚊,放射性物質發(fā)生泄漏器虾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一蹦锋、第九天 我趴在偏房一處隱蔽的房頂上張望兆沙。 院中可真熱鬧,春花似錦晕粪、人聲如沸挤悉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽装悲。三九已至,卻和暖如春尚氛,著一層夾襖步出監(jiān)牢的瞬間诀诊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工阅嘶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留属瓣,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓讯柔,卻偏偏與公主長得像抡蛙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魂迄,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內容