相信你們早已對這個問題爛熟於心,但仍是帶你們一塊兒過一下.bash
在MRC時代,系統斷定一個對象是否銷燬是根據這個對象的引用計數器來判斷的.其中每一個對象被建立時引用計數都爲1,每當對象被其餘指針引用時,須要手動使用[obj retain];讓該對象引用計數+1,當指針變量不在使用這個對象的時候,須要手動釋放release這個對象。 讓其的引用計數-1,當一個對象的引用計數爲0的時候,系統就會銷燬這個對象.總的來講在MRC模式下必須遵循誰建立,誰釋放,誰引用,誰管理
app
若是在MRC下使用ARC:
在Build Phases的Compile Sources中選擇須要使用MRC方式的.m文件,而後雙擊該文件在彈出的會話框中輸入 -fobjc-arc
less
ARC自動內存管理:
WWDC2011和iOS5所引入自動管理機制——自動引用計數(ARC),它不是垃圾回收機制而是編譯器的一種特性。ARC管理機制與MRC手動機制差很少,只是再也不須要手動調用retain、release、autorelease;當你使用ARC時,編譯器會在在適當位置插入release和autorelease;ARC時代引入了strong強引用來帶代替retain,引入了weak弱引用.總結來講ARC是LLVM和Runtime配合的結果函數
在ARC下使用MRC方法:
在ARC工程中若是要使用MRC的須要在工程的Build Phases的Compile Sources中選擇須要使用MRC方式的.m文件,而後雙擊該文件在彈出的會話框中輸入 -fno-objc-arc
源碼分析
自動釋放池始於MRC時代,主要是用於 自動 對 釋放池內 對象 進行引用計數-1的操做,即自動執行release方法,在MRC中使用autoreleasepool必須在代碼塊內部手動爲對象調用autorelease把對象加入到的自動釋放池,系統會自動在代碼塊結束後,對加入自動釋放池中的對象發送一個release消息.無需手動調用release.ui
咱們在建立工程的時候默認建立的.m文件main函數有個autoreleasePool的建立,而咱們本身的代碼中也能夠建立autoreleasePool 對象:this
main函數裏的:spa
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
本身代碼中的:線程
- (void)testConst{
@autoreleasepool {
for (int i = 0; i<1000; i++) {
NSString *str1 = [NSString stringWithFormat:@"%ld",i];
}
}
}
複製代碼
在這段代碼裏會建立大量的臨時變量(先不考慮代碼合不合理哈),就會消耗過多的內存空間,因此在開發過程當中,若是當遇到須要建立、使用大量的臨時變量時,能夠將相關的代碼放在autoreleasePool中進行,當出了@autoreleasepool {},這些臨時變量便會自動的進行釋放.debug
咱們想看下autoreleasePool的底層實現該如何?
通常咱們想看系統底層實現,通常有兩個途徑:第一是LLVM源碼分析 第二個就是clang-rewrite-objc 進行底層編譯,如今咱們先用第二種方式進行探究:
將.m文件 clang-rewrite-objc -o main.cpp
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
return UIApplicationMain(argc, argv,
__null,
NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")))
);
}
}
複製代碼
咱們看到了@autoreleasepool {} 底層編譯成 __AtAutoreleasePool __autoreleasepool.
咱們一塊兒來看下__AtAutoreleasePool結構:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
複製代碼
原來__AtAutoreleasePool 是個結構體,結構體裏有:
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}和
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}這兩個函數,其實__AtAutoreleasePool(){}是構造函數,而~__AtAutoreleasePool() {}這個是析構函數,其實也好理解當@autoreleasepool {}剛建立的時候底層會對atautoreleasepoolobj進行構造,當出了@autoreleasepool {} 方法區域的時候,底層會對atautoreleasepoolobj進行析構pop出去.
可是objc_autoreleasePoolPush、objc_autoreleasePoolPop 到底作了什麼事呢?咱們只有從源碼中找到答案了,請看下面的分析 autoreleasePool的進階.
想要了解底層,必需要有源碼,首先咱們在網上 http://www.opensource.apple.com/apsl/
裏下一份objc的源碼經過一些配置導入咱們的工程.(最新的版本好像是objc4-779.1版本)
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
複製代碼
AutoreleasePoolPage 又是什麼東西呢?點進去看看:
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
.
.
.
}
複製代碼
原來AutoreleasePoolPage繼承於AutoreleasePoolPageData, AutoreleasePoolPageData 結構以下:
struct AutoreleasePoolPageData
{
magic_t const magic; // 16
__unsafe_unretained id *next; //8
pthread_t const thread; // 8
AutoreleasePoolPage * const parent; //8
AutoreleasePoolPage *child; //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)
{
}
};
複製代碼
原來AutoreleasePoolPageData是個結構體,裏面有本身的一些成員變量,如:magic_t的結構體變量、next指針類型變量、depth、hiwat變量、還有parent、child指針成員變量,
其中指針類型佔8個字節,uint32_t類型佔4個字節,結構體類型取決於結構體自己的大小,好比magic_t結構體 ,裏面是:
static const uint32_t M0 = 0xA1A1A1A1;
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];
複製代碼
有的盆友會說到 int32_t M0、size_t M1_len 各佔4個字節,可是忽略了一點:
static const,靜態變量.靜態變量類型數據大小是放在內存裏的全局數據段裏,不在堆區,
因此magic_t結構體所佔的大小是uint32_t m[4]的大小,一個uint32_t 爲四字節,整個就是 4*4爲16個字節.
因此AutoreleasePoolPageData結構體本身變量共佔 56個字節.各個變量所佔大小已在上面標註,其中各個變量所表明的意思以下:
總結一下AutoreleasePoolPage其實就是一個雙向鏈表結構,AutoreleasePoolPage(自動釋放池頁) 用來存放 autorelease 的對象,可是每一頁的大小是有限制的,假如某個AutoreleasePoolPage頁中須要存放的autorelease 的對象過多,一頁存放不完,因此它就須要指向父結點點,在指向父結點裏的AutoreleasePoolPage頁中繼續存放.
那麼每一頁大小時所少呢?
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MIN_SIZE; // size and alignment, power of 2
.
.
.
#endif
}複製代碼
有個PAGE_MAX_SIZE,點擊進去是4096.原來每一頁AutoreleasePoolPage能夠存放4096個字節.一共4096個字節, (4096 - AutoreleasePoolPage 中本身成員變量所佔的字節)/每一個對象中所佔的字節. (4096 - 56)/8 = 505. 好的,每一AutoreleasePoolPage能夠存放505個對象.下面把AutoreleasePoolPage 結構圖放在下面,僅供你們參考.
結構弄清楚以後,咱們繼續跟代碼:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::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;
}
複製代碼
程序代碼回來到else的判斷裏, dest = autoreleaseFast(POOL_BOUNDARY);這個POOL_BOUNDARY 就至關於 AutoreleasePoolPage 裏的邊界,一些技術書籍裏也稱做是哨兵對象,這個對象頗有用的,等下就來看蘋果爲什麼這麼設計:
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);
}
}
複製代碼
以前經過分析代碼一進入這個 @autoreleasepool {}時,就會調用objc_autoreleasePoolPush方法,接着來到了 autoreleaseFast 方法,AutoreleasePoolPage *page = hotPage();取的此時的page,由於第一次建立 page確定取不到,因此會來到autoreleaseNoPage(obj)方法,
id *autoreleaseNoPage(id obj)
{
ASSERT(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
return page->add(obj);
}
複製代碼
一步步執行代碼發現 會來到:
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
return page->add(obj);複製代碼
建立個new AutoreleasePoolPage,而且setHotPage,緊接着將咱們的哨兵對象加入到AutoreleasePoolPage.也就是page->add(POOL_BOUNDARY);操做.
剛剛咱們在3.1的時候說到,@autoreleasepool {} 會建立個new AutoreleasePoolPage,這個AutoreleasePoolPage 會將POOL_BOUNDARY 添加進來,那麼何時會添加自動釋放的對象呢?
確定是當某個對象調用 autorelease方法 的時候,AutoreleasePoolPage 會把調用的對象加進來.
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"objc = %@",objc);
}複製代碼
回來到底層objc:
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
複製代碼
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
複製代碼
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
複製代碼
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
複製代碼
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);
}
}
複製代碼
終於來到這裏了,AutoreleasePoolPage *page = hotPage(); 獲取page,在3.1的時候page已經建立了,因此他回來到 if (page && !page->full()) ,看看本頁(AutoreleasePoolPage)有沒有滿,沒滿的話直接添加 page->add(obj);若是滿的話來到autoreleaseFullPage(obj, page);
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
複製代碼
首先來到這裏是個do-while 循環,while (page->full()); 這個方法就是由於以前的那個page滿了纔會來到這裏,因此這個條件必然知足.而後進行判斷 page是否有子節點了,若是有的話直接用子節點的page,若是沒有的話那隻能新建立個page了,而後新建立的page 父節點指向原來的節點.最後page->add(obj).
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
複製代碼
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
// 第一個節點 - 沒有父節點
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
複製代碼
這裏面:
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
}複製代碼
進行判斷,pop何時結束,確定是自動釋放池頁pop到那個哨兵對象的時候纔算所有釋放完,因此*stop != POOL_BOUNDARY,除非是壞節點,要否則*stop == POOL_BOUNDARY,
緊接着popPage<false>(token, page, stop);
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
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();
}
}
}
複製代碼
將自動釋放池頁 kill,包括子頁child,父頁parent 都進行kill.使全部的autorlease對象都進行釋放.
在APP中,整個主線程是運行在一個自動釋放池中的。
main函數中的自動釋放池的做用:這個池塊給出了一個pop點來顯式的告訴咱們這裏有一個釋放點,若是你的main在初始化的過程當中有別的內容能夠放在這裏。
使用@autoreleasepool
標記,調用push()方法。
沒有hotpage,調用
()
,設置EMPTY_POOL_PLACEHOLDER
。
由於設置了EMPTY_POOL_PLACEHOLDER
,因此會設置本頁爲hotpage
,添加邊界標記POOL_BOUNDARY
,最後添加obj。
繼續有對象調用autorelease
,此時已經有了page,調用page->add(obj)
。
若是page滿了,調用autoreleaseFullPage()
建立新page,重複第6點。
到達autoreleasePool邊界,調用pop方法,一般狀況下會釋放掉POOL_BOUNDARY
以後的全部對象