引用計數(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中葛碧,查看底層代碼
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);
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
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] 會立即釋放