內(nèi)存管理-MRC與ARC

引用計數(shù)

在iOS中,使用引用計數(shù)來管理OC對象的內(nèi)存
1陪蜻、一個新創(chuàng)建的OC對象引用計數(shù)默認是1,當(dāng)引用計數(shù)減為0贱鼻,OC對象就會銷毀宴卖,釋放其占用的內(nèi)存空間
2、調(diào)用retain會讓OC對象的引用計數(shù)+1邻悬,調(diào)用release會讓OC對象的引用計數(shù)-1
3症昏、引用計數(shù)存在優(yōu)化過的isa指針中(19位存放引用計數(shù),不夠存儲的時候has_sidetable_rc變?yōu)?父丰,若不夠存儲就存到SideTable中的refcountMap散列表中
4肝谭、當(dāng)調(diào)用alloc、new蛾扇、copy攘烛、mutableCopy方法返回了一個對象,在不需要這個對象時镀首,要調(diào)用release或者autorelease來釋放它

MRC Manual Reference Counting 手動引用計數(shù)(手動內(nèi)存管理)

ARC Automatic Reference Counting 自動引用計數(shù)(自動內(nèi)存管理)

一坟漱、MRC

簡單的說:誰retain誰release

@interface ViewController ()
//retain 引用計數(shù)+1
@property (retain, nonatomic) NSMutableArray *data;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
 //三種方式
//1、最原始的寫法  
// self.data = [[NSMutableArray alloc] init];
// [self.data release];

//2更哄、寫法不需要release 內(nèi)部已經(jīng)autorelease芋齿、  
 //self.data = [NSMutableArray array];

 // 3腥寇、autorelease 不需要再去release   
 //self.data = [[[NSMutableArray alloc] init] autorelease];
}

- (void)dealloc {
    self.data = nil;
    [super dealloc];
}

二、ARC

ARC 都幫我們做了什么沟突?
LLVM + Runtime互相協(xié)調(diào)花颗, ARC 利用LLVM編譯器自動幫我生成release和retain,autorelease相當(dāng)于開啟了ARC惠拭,弱引用就要用到Runtime扩劝,在程序運行過程中監(jiān)控到對象銷毀的時候,會把對象對應(yīng)的弱引用都清空职辅,不是編譯器的功勞是runtime功勞

自動釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool棒呛、AutoreleasePoolPage

調(diào)用了autorelease的對象最終都是通過AutoreleasePoolPage對象來管理的

看實例,簡單的類創(chuàng)建和釋放過程

1、自動給釋放池

   @autoreleasepool {
            MJPerson *person = [[[MJPerson alloc] init] autorelease];
    }

2域携、編譯c++代碼如下

 {
    __AtAutoreleasePool __autoreleasepool;
    MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
 }

3簇秒、c++代碼簡化后,申明了__AtAutoreleasePool局部變量秀鞭,創(chuàng)建MJPerson類

 {
 __AtAutoreleasePool __autoreleasepool;//申明局部變量 里面是一個結(jié)構(gòu)體
 MJPerson *person = [[[MJPerson alloc] init] autorelease];//創(chuàng)建person
 }

4趋观、__AtAutoreleasePool再往底層,里面實際是一個結(jié)構(gòu)體,結(jié)構(gòu)體里是一個構(gòu)造函數(shù)锋边,一個析構(gòu)函數(shù)

 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 構(gòu)造函數(shù)皱坛,在創(chuàng)建結(jié)構(gòu)體的時候調(diào)用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
 
    ~__AtAutoreleasePool() { // 析構(gòu)函數(shù),在結(jié)構(gòu)體銷毀的時候調(diào)用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void * atautoreleasepoolobj;
 };

5豆巨、所以又把最初的自動釋放池創(chuàng)建類簡化為三行代碼剩辟,主要是 構(gòu)造函數(shù):atautoreleasepoolobj = objc_autoreleasePoolPush(); 及析構(gòu)函數(shù):objc_autoreleasePoolPop(atautoreleasepoolobj);

@autoreleasepool {
           MJPerson *person = [[[MJPerson alloc] init] autorelease];
    }

簡化后

 atautoreleasepoolobj = objc_autoreleasePoolPush();構(gòu)造函數(shù)
 MJPerson *person = [[[MJPerson alloc] init] autorelease];
 objc_autoreleasePoolPop(atautoreleasepoolobj);//析構(gòu)函數(shù)

5.1 構(gòu)造函數(shù)push atautoreleasepoolobj = objc_autoreleasePoolPush();

c語言的objc_autoreleasePoolPush()會調(diào)用c++類的AutoreleasePoolPage::push()方法

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

push底層內(nèi)部實現(xiàn)

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

5.2 析構(gòu)函數(shù)pop objc_autoreleasePoolPop(atautoreleasepoolobj);

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

pop內(nèi)部實現(xiàn)

template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            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();
            }
        }
    }

5.3 調(diào)用c++中push中有autoreleaseNewPage, pop中AutoreleasePoolPage往扔,所以都用到AutoreleasePoolPage贩猎,因此自動釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage

6萍膛、__AtAutoreleasePool內(nèi)部是兩個結(jié)構(gòu)體吭服,objc_autoreleasePoolPush以下簡稱push, objc_autoreleasePoolPop 簡稱push

這兩個里又用到AutoreleasePoolPage數(shù)據(jù)結(jié)構(gòu)蝗罗,所以底層都是靠AutoreleasePoolPage來管理噪馏,調(diào)用autorelease的對象最終都是通過AutoreleasePoolPage對象來管理的
去NSObject中查看AutoreleasePoolPage類,objc4源碼:NSObject.mm

6.1 AutoreleasePoolPage類中的有用的成員變量

成員變量

6.2 AutoreleasePoolPage內(nèi)部結(jié)構(gòu)绿饵,0x1000-0x2000欠肾,共4096個字節(jié)

6.2.1 、0x1000-0x1038段存成員變量

由圖可知0x1000轉(zhuǎn)十進制為4096------0x1038轉(zhuǎn)十進制4152,一共56個字節(jié)拟赊,用來存儲成員變量

6.2.2 刺桃、0x1038-0x2000存autorelease對象的地址4040個字節(jié)

0x1038轉(zhuǎn)十進制為4152------0x2000轉(zhuǎn)十進制8192,一共4040個字節(jié),用來存放autorelease對象的地址

6.2.3 吸祟、 AutoreleasePoolPage對象通過雙向鏈表鏈接

所有的AutoreleasePoolPage對象通過雙向鏈表的形式連接在一起瑟慈,用child指針指向下一個AutoreleasePoolPage桃移,用parent指針指向上一個AutoreleasePoolPage
若剩下的(0x1038到0x2000之間)4040個字節(jié)中不夠存放autorelease對象,會創(chuàng)建下一個 AutoreleasePoolPage對象(一般一個對象8個字節(jié))


AutoreleasePoolPage內(nèi)部結(jié)構(gòu)
AutoreleasePoolPage中葛碧,查看底層代碼
begin() 實現(xiàn),返回的是這個指針的地址和大小
id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }
end() 實現(xiàn)借杰,得到整個地址大小SIZE=4096個字節(jié)
 id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

注意:不是一個person對象對應(yīng)創(chuàng)建一個AutoreleasePoolPage,也不是一個pool池創(chuàng)建一個AutoreleasePoolPage进泼,是放不下的時候才會創(chuàng)建多個蔗衡,如1000個person對象至少需要8000個字節(jié),因為一個AutoreleasePoolPage中能存儲autorelease對象一共是4040個乳绕,那么至少是需要兩個AutoreleasePoolPage才夠存放绞惦,存放方式

6.3 AutoreleasePoolPage中進棧出棧過程(棧不是指的棧區(qū),這部分存在數(shù)據(jù)塊區(qū))

push過程

6.3.1 一個AutoreleasePool

一個對象調(diào)用objc_autoreleasePoolPush(push)方法會將一個POOL_BOUNDARY等于0入棧洋措,表示邊界济蝉,并且返回其存放的內(nèi)存地址
POOL_BOUNDARY放在autorelease的首位置 接下來的位置存放person1 person2 .。菠发。王滤。

 @autoreleasepool {
            // atautoreleasepoolobj = objc_autoreleasePoolPush();
        MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
        MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
            //        objc_autoreleasePoolPop(atautoreleasepoolobj);
        }

查看源碼
objc_autoreleasePoolPush是將一開始的POOL_BOUNDARY壓入page棧中,再把對象地址返回(可以自行查看源碼)滓鸠,不夠喲過時創(chuàng)建下一個page

 page->add(POOL_BOUNDARY);
page中autorelease對象的存放

6.3.1 多個AutoreleasePool嵌套

@autoreleasepool { //  r1 = push()
        
        MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
        MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
        MJPerson *p21 = [[[MJPerson alloc] init] autorelease];
        MJPerson *p22 = [[[MJPerson alloc] init] autorelease];
        MJPerson *p23 = [[[MJPerson alloc] init] autorelease];
        @autoreleasepool { // r2 = push()
            for (int i = 0; i < 600; i++) {
                MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
            }
            @autoreleasepool { // r3 = push()
                MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
              _objc_autoreleasePoolPrint();
            } // pop(r3)
        } // pop(r2)
    } // pop(r1)

打印結(jié)果,可以看出換頁了淑仆,有full hot標記

objc[37067]: ##############
objc[37067]: AUTORELEASE POOLS for thread 0x1000e7e00
objc[37067]: 609 releases pending.
bjc[37067]: [0x106009000]  ................  PAGE (full)  (cold)
objc[37067]: [0x106009038]  ################  POOL 0x106009038
objc[37067]: [0x106009040]       0x10052b610  MJPerson
objc[37067]: [0x106009048]       0x10052ac30  MJPerson
objc[37067]: [0x106009050]       0x100529dc0  MJPerson
objc[37067]: [0x106009058]       0x100529830  MJPerson
objc[37067]: [0x106009060]       0x100529350  MJPerson
objc[37067]: [0x106009068]  ################  POOL 0x106009068
objc[37067]: [0x106009070]       0x1005294b0  MJPerson
objc[37067]: [0x106009078]       0x100524da0  MJPerson
objc[37067]: [0x106009080]       0x100529040  MJPerson
.
.
.省略
objc[37067]: [0x106009ff8]       0x10052f380  MJPerson
objc[37067]: [0x100810000]  ................  PAGE  (hot) 
objc[37067]: [0x100810038]       0x10052f390  MJPerson
objc[37067]: [0x100810040]       0x10052f3a0  MJPerson
objc[37067]: [0x100810048]       0x10052f3b0  MJPerson
.
.
objc[37067]: [0x100810360]       0x10052f9e0  MJPerson
objc[37067]: [0x100810368]  ################  POOL 0x100810368
objc[37067]: [0x100810370]       0x10052f9f0  MJPerson
objc[37067]: ##############

注:............... PAGE (full) (cold)表示已滿,................ PAGE (hot) 表示當(dāng)前活躍的page

這里需要至少兩個page,里面的存放方式是每一個autoreleasepool的push都會有一個POOL_BOUNDARY


跨page存放

pop 過程

查看源碼

template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            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();
            }
        }
    }

page->releaseUntil(stop);有一句這個代碼哥力,release直到stop,stop的標記就是之前的POOL_BOUNDARY位置

總結(jié)

整個ARC的過程實際就是進棧出棧過程墩弯,其中最底層由AutoreleasePoolPage來管理

7吩跋、autorelease釋放時機

存在兩種情況
1、有 @autoreleasepool {}
這種情況的調(diào)用時機就是上面的講解過程渔工,就是調(diào)用pop的時候就會釋放锌钮,也就是大括號結(jié)束的地方
2、沒有autoreleasepool大括號引矩, MJPerson *person = [[[MJPerson alloc] init] autorelease];這種情況

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"1");
    MJPerson *person = [[MJPerson alloc] init];
    NSLog(@"3");
    NSLog(@"%s", __func__);
    
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    NSLog(@"%s", __func__);
}

運行結(jié)果

2021-05-27 13:21:22.093420+0800 Interview18-autorelease時機[37726:5017612] 1
2021-05-27 13:21:22.093579+0800 Interview18-autorelease時機[37726:5017612] 3
2021-05-27 13:21:22.093675+0800 Interview18-autorelease時機[37726:5017612] -[ViewController viewDidLoad]
2021-05-27 13:21:22.093770+0800 Interview18-autorelease時機[37726:5017612] -[MJPerson dealloc]
2021-05-27 13:21:22.102455+0800 Interview18-autorelease時機[37726:5017612] -[ViewController viewWillAppear:]
2021-05-27 13:21:22.112922+0800 Interview18-autorelease時機[37726:5017612] -[ViewController viewDidAppear:]

根據(jù)答應(yīng)結(jié)果可能會回答釋放時機是在viewDidLoad之后梁丘,viewWillAppear之前,這種回答是很片面的

釋放時機全面回答:

與runloop有關(guān)旺韭。會在所處的loop休眠之前進行release
iOS在主線程的Runloop中注冊了2個Observer
第1個Observer監(jiān)聽了kCFRunLoopEntry事件氛谜,會調(diào)用objc_autoreleasePoolPush()
第2個Observer監(jiān)聽了kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()区端、objc_autoreleasePoolPush()
監(jiān)聽了kCFRunLoopBeforeExit事件值漫,會調(diào)用objc_autoreleasePoolPop()

問題: 方法里有局部對象, 出了方法后會立即釋放嗎
1织盼、如果局部對象 是在autorelease里
在某次RunLoop循環(huán)中杨何,RunLoop休眠之前調(diào)用了release
2酱塔、 如果arc生成的調(diào)用release
代碼[p release] 會立即釋放

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市危虱,隨后出現(xiàn)的幾起案子羊娃,更是在濱河造成了極大的恐慌,老刑警劉巖埃跷,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕊玷,死亡現(xiàn)場離奇詭異,居然都是意外死亡捌蚊,警方通過查閱死者的電腦和手機集畅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缅糟,“玉大人挺智,你說我怎么就攤上這事〈盎拢” “怎么了赦颇?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赴涵。 經(jīng)常有香客問我媒怯,道長,這世上最難降的妖魔是什么髓窜? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任扇苞,我火速辦了婚禮,結(jié)果婚禮上寄纵,老公的妹妹穿的比我還像新娘鳖敷。我一直安慰自己,他們只是感情好程拭,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布定踱。 她就那樣靜靜地躺著,像睡著了一般恃鞋。 火紅的嫁衣襯著肌膚如雪崖媚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天恤浪,我揣著相機與錄音畅哑,去河邊找鬼。 笑死水由,一個胖子當(dāng)著我的面吹牛敢课,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼直秆,長吁一口氣:“原來是場噩夢啊……” “哼濒募!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起圾结,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瑰剃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后筝野,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晌姚,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年歇竟,在試婚紗的時候發(fā)現(xiàn)自己被綠了挥唠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡焕议,死狀恐怖宝磨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盅安,我是刑警寧澤唤锉,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站别瞭,受9級特大地震影響窿祥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝙寨,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一晒衩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墙歪,春花似錦听系、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弃秆。三九已至届惋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菠赚,已是汗流浹背脑豹。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留衡查,地道東北人瘩欺。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俱饿。 傳聞我的和親對象是個殘疾皇子歌粥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容