內(nèi)存管理面試題
使用CADisplayLink、NSTimer有什么注意點?
介紹下內(nèi)存的幾大區(qū)域
講一下你對 iOS 內(nèi)存管理的理解
ARC 都幫我們做了什么?
LLVM + Runtime
weak指針的實現(xiàn)原理
autorelease對象在什么時機會被調(diào)用release
方法里有局部對象, 出了方法后會立即釋放嗎
CADisplayLink、NSTimer使用注意

GCD定時器
NSTimer依賴于RunLoop,如果RunLoop的任務過于繁重,可能會導致NSTimer不準時
而GCD的定時器會更加準時

iOS程序的內(nèi)存布局
保留
代碼段:編譯之后的代碼
-
數(shù)據(jù)段
字符串常量:比如NSString *str = @"123"
已初始化數(shù)據(jù):已初始化的全局變量、靜態(tài)變量等
未初始化數(shù)據(jù):未初始化的全局變量、靜態(tài)變量等
棧:函數(shù)調(diào)用開銷,比如局部變量。分配的內(nèi)存空間地址越來越小
堆:通過alloc、malloc、calloc等動態(tài)分配的空間,分配的內(nèi)存空間地址越來越大
內(nèi)核區(qū)
Tagged Pointer
從64bit開始,iOS引入了Tagged Pointer技術,用于優(yōu)化NSNumber、NSDate、NSString等小對象的存儲
在沒有使用Tagged Pointer之前, NSNumber等對象需要動態(tài)分配內(nèi)存、維護引用計數(shù)等,NSNumber指針存儲的是堆中NSNumber對象的地址值
使用Tagged Pointer之后,NSNumber指針里面存儲的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲在了指針中
當指針不夠存儲數(shù)據(jù)時,才會使用動態(tài)分配內(nèi)存的方式來存儲數(shù)據(jù)
objc_msgSend能識別Tagged Pointer,比如NSNumber的intValue方法,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開銷
-
如何判斷一個指針是否為Tagged Pointer?
iOS平臺,最高有效位是1(第64bit)
Mac平臺,最低有效位是1
OC對象的內(nèi)存管理
在iOS中,使用引用計數(shù)來管理OC對象的內(nèi)存
一個新創(chuàng)建的OC對象引用計數(shù)默認是1,當引用計數(shù)減為0,OC對象就會銷毀,釋放其占用的內(nèi)存空間
調(diào)用retain會讓OC對象的引用計數(shù)+1,調(diào)用release會讓OC對象的引用計數(shù)-1
-
內(nèi)存管理的經(jīng)驗總結(jié)
當調(diào)用alloc、new、copy、mutableCopy方法返回了一個對象,在不需要這個對象時,要調(diào)用release或者autorelease來釋放它
想擁有某個對象,就讓它的引用計數(shù)+1;不想再擁有某個對象,就讓它的引用計數(shù)-1
-
可以通過以下私有函數(shù)來查看自動釋放池的情況\
extern void _objc_autoreleasePoolPrint(void);
引用計數(shù)的存儲

Dealloc
當一個對象要釋放時,會自動調(diào)用dealloc,接下的調(diào)用軌跡是
- dealloc
- _objc_rootDealloc
- rootDealloc
- object_dispose
- objc_destructInstance、free

**
自動釋放池
自動釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage
調(diào)用了autorelease的對象最終都是通過AutoreleasePoolPage對象來管理的
-
源碼分析
clang重寫@autoreleasepool
objc4源碼:NSObject.mm

AutoreleasePoolPage的結(jié)構(gòu)
- 每個AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存,除了用來存放它內(nèi)部的成員變量,剩下的空間用來存放autorelease對象的地址
- 所有的AutoreleasePoolPage對象通過雙向鏈表的形式連接在一起
- 調(diào)用push方法會將一個POOL_BOUNDARY入棧,并且返回其存放的內(nèi)存地址
- 調(diào)用pop方法時傳入一個POOL_BOUNDARY的內(nèi)存地址,會從最后一個入棧的對象開始發(fā)送release消息,直到遇到這個POOL_BOUNDARY
- id *next指向了下一個能存放autorelease對象地址的區(qū)域
Runloop和Autorelease
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()