在開發(fā)中,內(nèi)存管理是一個必要的技能,研究iOS 開發(fā),我們通過內(nèi)存布局、內(nèi)存管理方案、數(shù)據(jù)結(jié)構(gòu)、ARC/MRC、引用計數(shù)、弱引用、自動釋放池、循環(huán)引用這個八個方面去了解iOS 的內(nèi)存管理。
內(nèi)存布局

Stack:方法調(diào)用
Heap:通過alloc分配的對象
Bss:未初始化的靜態(tài)變量和全局變量
Data:已初始化的全局變量
Test:程序代碼
內(nèi)存管理方案
isa指針
OC對象里提到最多的就是isa指針了,大家都知道isa是指向該對象的內(nèi)存地址,其實(shí)這個地址里有一些特殊的含義。
在arm64架構(gòu)之前,isa就是一個普通的指針,存儲著Class、Meta-Class對象的內(nèi)存地址
從arm64架構(gòu)開始,對isa進(jìn)行了優(yōu)化,變成了一個共用體(union)結(jié)構(gòu),還使用位域來存儲更多的信息

- 1位:nonpointer,如果是0就代表著是一個純的isa指針,直接代表了當(dāng)前類對象的直接地址,如果是1代表著不僅存儲著地址,而且還包含著一些內(nèi)存管理方面的數(shù)據(jù),就是非指針類型的isa.
- 2位:has_assoc,0和1代表著是否含有關(guān)聯(lián)對象。如果沒有,釋放時會更快。
- 3位:has_cxx_dtor,是否有C++的析構(gòu)函數(shù)(.cxx_destruct),通過這個也能代表是通過ARC來進(jìn)行內(nèi)存管理的。如果沒有,釋放時會更快。
- 接下來33位:shiftcls,代表了當(dāng)前類對象的地址,需要把對應(yīng)位置的值拿出來,再去計算實(shí)際對應(yīng)的指針地址。存儲著Class、Meta-Class對象的內(nèi)存地址信息
- 后6位:magic用于在調(diào)試時分辨對象是否未完成初始化
- 1位:weakly_refrenced,標(biāo)識了該對象是否有弱引用指針。如果沒有,釋放時會更快。
- 1位:deallocting,標(biāo)識了當(dāng)前對象是否在進(jìn)行dealloc操作
- 1位:has_sidetable_rc,標(biāo)識了如果當(dāng)前對象如果已經(jīng)超過了引用計數(shù)上限,需要外掛一個sidetable去存儲相關(guān)的引用計數(shù)內(nèi)容,就是一個散列表
- 后續(xù)位:extra_rc,代表了額外的引用計數(shù),當(dāng)我們的引用計數(shù)在一個很小的范圍內(nèi)就會存在isa指針當(dāng)中,而不是存在另一個表里。
關(guān)于內(nèi)存管理不僅僅有散列表,其實(shí)還有extra_rc來存儲相關(guān)的引用計數(shù)值。
散列表內(nèi)存管理方案
iOS中是主要通過散列表去管理內(nèi)存。源碼當(dāng)中利用SideTables()結(jié)構(gòu)來實(shí)現(xiàn),sidetables實(shí)際上是一個哈希表,我們可以通過引用對象指針,來找到對應(yīng)的sidetable.

這里設(shè)計成多個sideTable的思想是,如果有一個table的話,我們在程序內(nèi)申請的所有對象的引用計數(shù)或者弱引用存儲就會放在一個大表中,這個時候如果我們要操作某個對象的引用值去進(jìn)行修改,包括release,retain,而且對象都是在不同的線程中,這個時候?qū)Ρ聿僮鞯臅r候需要進(jìn)行加鎖去處理,會十分影響效率。系統(tǒng)為了解決這個問題,引用了分離鎖的計數(shù)方案。
分離鎖:我們可以把內(nèi)存對象對應(yīng)的引用計數(shù)表,拆成多個部分,這樣對多個對象操作時候,就可以分散對多個表分別加鎖。比如某個對象在A表里,另個對象在B表里,那如果兩個對象要同時進(jìn)行引用計數(shù)操作的時候,就可以并發(fā)操作,如果在一張表里就需要挨著順序去操作表。
實(shí)現(xiàn)快速分流
那怎么去通過isa指針去快速的定位到是哪個sidetable表中,這里就提到了快速分流的方案Hash查找。
sidetables的本質(zhì)是一張hash表,一共有64張,來存儲引用計數(shù)。
hash的具體操作,就是以對象指針作為key,通過hash函數(shù),獲得對應(yīng)的sidetable作為value。我們回想一下OC中字典的使用方法,就基本對哈希有一個大概的認(rèn)識。
數(shù)據(jù)結(jié)構(gòu)
spinlock_t自旋鎖
是忙等的鎖,忙等指的是如果當(dāng)前線程已被其他鎖獲取,那么當(dāng)前線程就會不斷的探索鎖是否被釋放,如果釋放掉了,會去第一時間獲取這個鎖。自旋鎖適合輕量訪問。
refcountTable引用計數(shù)表
引用計數(shù)表實(shí)際上就是哈希算法的使用,通過哈希查找去定位引用計數(shù)的值,這個值就是一個unsigned long 類型值,共64位
- 1位 weakly_referenced,表示是否有弱引用。
- 2位 deallocing 表示是否正在釋放
- 其他位數(shù) 代表了引用計數(shù)值,實(shí)際上使用的時候需要向右平移兩位,才能算出正確的值
weak_table_t弱引用表
弱引用表也是一個哈希表,通過key去查找value,這里的value是一個結(jié)構(gòu)體數(shù)組

ARC MRC
這里只提一嘴,ARC實(shí)際上是有編譯器llvm和runtime共同作用才可以
引用計數(shù)管理
- alloc實(shí)現(xiàn)
經(jīng)過一系列調(diào)用,最終調(diào)用了c函數(shù)calloc
此時并沒有設(shè)置引用計數(shù)為1 - retain實(shí)現(xiàn),實(shí)際上是兩次哈希查找
SideTable& table = SideTables()[this] // 第一次哈希查找是從所有表中定位出當(dāng)前對象引用計數(shù)所在的那張表
size_t& refcntStorage = table.refcnts[this]// 第二次哈希是從表中找出引用計數(shù)的值
refcntStorage += SIDE_TABLE_RC_ONE // 這個函數(shù)是增加引用計數(shù),SIDE_TABLE_RC_ONE這個常量通過前邊提到的引用計數(shù)值的講解可以知道這個值是4,因?yàn)?4位中后兩位并不是引用計數(shù)的含義
- release實(shí)現(xiàn),方式同retain
SideTable& table = SideTables()[this]
RefcountMap::iterator it = table.fefcnts.find(this)
refcntStorage += SIDE_TABLE_RC_ONE
- retainCount實(shí)現(xiàn),從代碼中可以看出alloc的情況下,雖然引用計數(shù)沒有設(shè)置為1,但是retainCount的結(jié)果依然唯一,就是因?yàn)橛衦efcnt_result的存在
SideTable& table = SideTables()[this] ;
size_t refcnt_result = 1;
RefcountMap::iterator it = table.fefcnts.find(this)
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT
-
dealloc實(shí)現(xiàn)
dealloc實(shí)現(xiàn)



弱引用管理
id __weak obj1 = obj; //編譯前
id obj1; obj_initWeak(&obj1, obj)//編譯后
- 添加weak變量調(diào)用棧:
obj_initWeak()->storeWeak()->weak_register_no_lock()
通過NSObject的源碼可以看到是按上邊的三個函數(shù)的調(diào)用順序來處理,最終是由weak_register_no_lock函數(shù)進(jìn)行弱引用變量的添加,具體添加的位置是通過hash算法來查找,如果所查找的對象已經(jīng)查找到了所對應(yīng)的弱引用數(shù)組,那么就進(jìn)行數(shù)組的添加,如果沒有查找到就新建一個數(shù)組,添加到第0個位置,后邊的位置初始化為Nil。 - 清除weak變量,同時設(shè)置指向?yàn)閚il
dealloc()->...->weak_clear_no_lock()
通過源碼可以看出,當(dāng)一個對象在dealloc之后,會調(diào)用weak_clear_no_lock方法,首先對當(dāng)前對象進(jìn)行一個哈希查找,如果沒有在弱引用表中找到這個弱引用數(shù)組,那么直接返回,如果找到了,就遍歷這個數(shù)組,挨個設(shè)置為Nil。
自動釋放池AutoreleasePool
首先編譯器會將@autoreleasepool{}改寫為:
void * ctx = obj_autoreleasePoolPush(); 這個函數(shù)會調(diào)用void *AtuorelasePoolPage::push(void)
中間是{}中的代碼
之后objc_autoreleasePoolPop(ctx); 這個函數(shù)會調(diào)用 AutorelasePoolPage::pop(void *ctxt)
一次pop實(shí)際上相當(dāng)于一次批量的pop操作,就是說添加到autoreleasepool{}內(nèi)的變量,會在pop時候一次都釋放掉。
自動釋放池的數(shù)據(jù)結(jié)構(gòu)
- 是以棧為節(jié)點(diǎn)通過雙向鏈表的形式組合而成
AotoreleasePoolPage中包含的結(jié)構(gòu)信息
id *next; //指向棧當(dāng)中下一個可填充的位置。
AotoreleasePoolPage *const parent;//雙向鏈表中的父指針
AotoreleasePoolPage *child;//孩子指針
pthread_t const thread;//線程 -
AutoreleasePoolPage::push
- AutoorelleaasePoolPage::pop
根據(jù)傳入的哨兵對象找到對應(yīng)位置
給上次push操作之后添加的對象依次發(fā)送release消息
回退next指針到正確位置 - 是和線程一一對應(yīng)的
在當(dāng)次runloop將要結(jié)束的時候調(diào)用AutoreleasePoolPage::pop()
AutoreleasePool可以多層嵌套的原因就是多層嵌套就是插入哨兵對象
demo
函數(shù)中加入一個autoreloasePool時候,會在函數(shù)的開始和結(jié)束分別加入push和pop方法,這個是在編譯期就決定的
int main(int argc, const char * argv[]) {
@autoreleasepool {
// atautoreleasepoolobj = objc_autoreleasePoolPush();
for (int i = 0; i < 1000; i++) {
MJPerson *person = [[[MJPerson alloc] init] autorelease];
} // 8000個字節(jié)
// objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
// 下邊是將.m類編譯成c++文件后的源碼,編譯器會生成__AtAutoreleasePool的c++代碼,
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;
};
// main的核心代碼
{
__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"));
}
atautoreleasepoolobj = objc_autoreleasePoolPush();
MJPerson *person = [[[MJPerson alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
循環(huán)引用
三種循環(huán)引用
- 自循環(huán)引用
id __strong obj = self; - 相互循環(huán)引用
在B類中:id __strong obj = A;
在A類中:id __strong obj = B; - 多循環(huán)引用
A引用B,B引用C,C引用D,D引用A
如何破除循環(huán)引用
- 避免循環(huán)引用
__weak
__block:MRC下,__block修飾對象不回增加引用計數(shù),避免了循環(huán)引用。在ARC下,__block修飾對象會被強(qiáng)引用,無法避免循環(huán)引用,需要手動解環(huán)。
__unsafe_unretained:修飾對象不回增加引用計數(shù),避免了循環(huán)引用,如果被修飾對象在某一時機(jī)被釋放,會產(chǎn)生懸垂指針。 - 在合適的時機(jī)手動斷環(huán)
NSTimer的循環(huán)引用問題
增加一個中間對象,持有NSTimer和原對象的弱引用。在中間對象里持有的target進(jìn)行判斷,如果值存在就說明沒被釋放,就把NSTimer的值回調(diào)給target.
@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;
- (void)fire:(NSTimer *)timer;
@end
@implementation TimerWeakObject
- (void)fire:(NSTimer *)timer
{
if (self.target) {
if ([self.target respondsToSelector:self.selector]) {
[self.target performSelector:self.selector withObject:timer.userInfo];
}
}
else{
[self.timer invalidate];
}
}
@end
@implementation NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
{
TimerWeakObject *object = [[TimerWeakObject alloc] init];
object.target = aTarget;
object.selector = aSelector;
object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
return object.timer;
}


