內(nèi)存管理方式:
MRC:手動(dòng)管理內(nèi)存极谊,需要開(kāi)發(fā)人員管理內(nèi)存魏保,手動(dòng)調(diào)用Release,以控制對(duì)象內(nèi)存的釋放褂始。
ARC:自動(dòng)內(nèi)存管理,系統(tǒng)控制內(nèi)存的釋放時(shí)機(jī)描函,主要由AutoReleasePool管理崎苗,釋放時(shí)機(jī)有所延后,與RunLoop相關(guān)赘阀。
引用計(jì)數(shù):
iOS對(duì)象的內(nèi)存釋放主要是引用計(jì)數(shù)決定益缠,當(dāng)一個(gè)對(duì)象初始化開(kāi)始,每有一個(gè)指針指向該內(nèi)存地址基公,引用計(jì)數(shù)就會(huì)增加幅慌,調(diào)用Retain增加引用計(jì)數(shù),調(diào)用Release則減少引用計(jì)數(shù)轰豆,由哈希表管理每個(gè)對(duì)象的引用計(jì)數(shù)胰伍。
sideTable:
由sideTable管理,內(nèi)部包含引用計(jì)數(shù)表和weak指針表酸休。
struct SideTable {
spinlock_t slock;
# mark 引用計(jì)數(shù)表 key:對(duì)象地址 value:引用計(jì)數(shù)
RefcountMap refcnts;
# mark 存放weak指針的表 key:對(duì)象地址 value:對(duì)應(yīng)weak指針的數(shù)組
weak_table_t weak_table;
};
PS:
在底層代碼里會(huì)看到很多isTaggedPointer和nonpointer的判斷骂租,這兩者都是用于64位上的優(yōu)化機(jī)制,大概了解一下即可斑司。
isTaggedPointer:對(duì)NSNumber渗饮,NSString之類(lèi)常量的數(shù)據(jù)類(lèi)型進(jìn)行區(qū)別處理,將值直接保存在指針中宿刮,避免申請(qǐng)堆區(qū)的內(nèi)存空間互站,所以也不需要維護(hù)引用計(jì)數(shù)。
nonpointer:isa在64位上不再是一個(gè)單純的指針僵缺,還會(huì)存儲(chǔ)一些其他信息胡桃,nonpointer用來(lái)區(qū)別,兩種情況區(qū)別處理磕潮。
獲取引用計(jì)數(shù)的方法:
objc_object::rootRetainCount()
{
#mark
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
#mark兩種情況處理類(lèi)似翠胰,只是nonpointer的話,部分?jǐn)?shù)據(jù)可以直接在isa上取到
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
#mark 單純的指針類(lèi)型
return sidetable_retainCount();
}
objc_object::sidetable_retainCount()
{
#mark 通過(guò)對(duì)象的地址獲取對(duì)應(yīng)的表
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
release方法:
每次release調(diào)用都會(huì)減少引用計(jì)數(shù)自脯,當(dāng)引用計(jì)數(shù)小于臨界值時(shí)的時(shí)候之景,會(huì)調(diào)用析構(gòu)函數(shù)dealloc。
- (oneway void)release {
((id)self)->rootRelease();
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
#mark rootRelease內(nèi)部相關(guān)邏輯較多冤今,包含指針類(lèi)型判斷和數(shù)據(jù)異常處理闺兢,節(jié)省篇幅,這里只有部分……
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
#mark 加鎖保護(hù),真正處理的函數(shù)為sidetable_release屋谭。
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
……
}
sidetable_release方法的處理:
1脚囊、根據(jù)對(duì)象獲取對(duì)應(yīng)的sideTable
2、判斷引用計(jì)數(shù)是否小于臨界值
2桐磁、引用計(jì)數(shù)小于臨界值的悔耘,標(biāo)記為需要釋放,否則進(jìn)行-1我擂。
4衬以、如果標(biāo)記為需要釋放,則調(diào)用dealloc方法校摩。
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
#mark 獲取到SideTable
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
#mark 表里找不到
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
#mark 引用計(jì)數(shù)小于臨界值了看峻,需要釋放
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
#mark 引用計(jì)數(shù)-1
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
#mark 需要釋放
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);//發(fā)送消息調(diào)用dealloc銷(xiāo)毀對(duì)象。
}
return do_dealloc;
}
dealloc方法:
對(duì)象自身的釋放和對(duì)象相關(guān)變量和關(guān)聯(lián)數(shù)據(jù)的處理衙吩。
sideTable的處理互妓,清除引用計(jì)數(shù)記錄,釋放weak指針坤塞。
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
#nark 沒(méi)有其他關(guān)聯(lián)的數(shù)據(jù)或者表需要處理冯勉,直接釋放
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
#mark object_dispose方法:
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
#mark 釋放對(duì)象的實(shí)例變量
if (cxx) object_cxxDestruct(obj);
#mark 移除動(dòng)態(tài)關(guān)聯(lián)的對(duì)象
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
接下來(lái)是clearDeallocating方法:
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
sidetable_clearDeallocating和clearDeallocating_slow內(nèi)部處理相似,通過(guò)weak表找到指向該對(duì)象的weak指針并釋放摹芙,有引用計(jì)數(shù)記錄的灼狰,則清除該記錄。
objc_object::sidetable_clearDeallocating()
{
SideTable *table = SideTable::tableForPointer(this);
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
spinlock_lock(&table->slock);
RefcountMap::iterator it = table->refcnts.find(this);
if (it != table->refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
#mark 如果有弱引用浮禾,還需要清理weak指針表
weak_clear_no_lock(&table->weak_table, (id)this);
}
#mark 清理引用計(jì)數(shù)記錄
table->refcnts.erase(it);
}
spinlock_unlock(&table->slock);
}
釋放weak指針
獲取weak的hash表交胚,根據(jù)對(duì)象地址獲取所有指向該對(duì)象的weak指針數(shù)組,將數(shù)組內(nèi)元素置為nil盈电。
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
#mark 遍歷數(shù)組承绸,將指向?qū)ο蟮膚eak指針置為nil。
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak指針:
初始化weak指針的時(shí)候挣轨,會(huì)調(diào)用objc_initWeak函數(shù)。
當(dāng)weak指針指向一個(gè)對(duì)象時(shí)轩猩,會(huì)調(diào)用objc_storeWeak函數(shù)卷扮。
當(dāng)weak指針不再指向任何對(duì)象時(shí),銷(xiāo)毀會(huì)調(diào)用objc_destroyWeak函數(shù)均践。
//objc_initWeak
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
//objc_storeWeak:
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
//objc_destroyWeak
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
可以看出晤锹,三個(gè)方法的最終走向都是storeWeak方法,storeWeak的內(nèi)部實(shí)現(xiàn):
storeWeak內(nèi)部邏輯主要是由 haveOld 和haveNew 判斷彤委,
haveOld 代表weak指針是否已經(jīng)指向其他老對(duì)象了鞭铆,haveNew 代表需要指向新的對(duì)象了 。所以正常情況下是:
objc_initWeak: haveOld = NO,haveNew = YES车遂。
objc_storeWeak: haveOld = YES封断,haveNew = YES。
objc_destroyWeak:haveOld = YES舶担,haveNew = NO坡疼。
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
#mark有老對(duì)象則有oldTable
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
#mark有新對(duì)象則有newTable
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
#mark 異常處理
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
#mark 異常處理
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
#mark 從老對(duì)象的weak表里移除記錄
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
#mark 綁定新對(duì)象的weak表
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
自動(dòng)釋放池autoreleasepool:
經(jīng)常看到這樣的代碼:
for (int i = 0 ; i< 555500; i++) {
@autoreleasepool {
TestARCObject *object = [[TestARCObject alloc]init];
[object doSomeThing ];
}
}
在創(chuàng)建大量的臨時(shí)變量時(shí)衣陶,需要手動(dòng)添加自動(dòng)釋放池柄瑰,如果不添加自動(dòng)釋放池的時(shí)候,就會(huì)導(dǎo)致內(nèi)存突然暴漲剪况。因?yàn)锳RC跟MRC不同教沾,MRC是程序員可以手動(dòng)釋放每一個(gè)指針,而ARC下指針的釋放是由autoreleasepool管理译断,然后批量進(jìn)行release處理的授翻。
如果我們自己不添加autoreleasepool,那么將會(huì)由系統(tǒng)自動(dòng)創(chuàng)建的autoreleasepool管理镐作,就是這個(gè) :
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool { #mark 包裹在最外面的autoreleasepool
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
那么這個(gè)主線程的自動(dòng)釋放池什么時(shí)候?qū)?nèi)部的指針進(jìn)行release呢藏姐,雖然官方只是輕描淡寫(xiě)的一句“將會(huì)在合適的時(shí)候釋放”,但是我們都能猜出其實(shí)是和RunLoop相關(guān)该贾。
通過(guò)控制臺(tái)打印出主線程RunLoop相關(guān)信息:
搜索autoRelease相關(guān)數(shù)據(jù)羔杨,可以找到_wrapRunLoopWithAutoreleasePoolHandler方法:
<CFRunLoopObserver 0x600001fcc640 [0x7fff8062ce40]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
<CFRunLoopObserver 0x600001fcc6e0 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
可以看到activities有兩個(gè)值,也就是說(shuō)兩種情況下會(huì)觸發(fā)AutoreleasePool相關(guān)的回調(diào)方法杨蛋,再查看RunLoop的狀態(tài)枚舉:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), 1
kCFRunLoopBeforeTimers = (1UL << 1), 2
kCFRunLoopBeforeSources = (1UL << 2), 4
kCFRunLoopBeforeWaiting = (1UL << 5), 32
kCFRunLoopAfterWaiting = (1UL << 6), 64
kCFRunLoopExit = (1UL << 7), 128 兜材,
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
activities = 0x1(1)對(duì)應(yīng)kCFRunLoopEntry,也就是進(jìn)入runLoop狀態(tài)逞力,執(zhí)行autoreleasepool相關(guān)初始化操作曙寡,
activities = 0xa0(160 )對(duì)應(yīng)kCFRunLoopExit | kCFRunLoopBeforeWaiting(32+128),也就是退出Runloop和即將進(jìn)入休眠的狀態(tài)寇荧,執(zhí)行autoreleasepool相關(guān)釋放操作举庶。
使用clang命令行轉(zhuǎn)換后可以看到:
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"))));
}
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
可以看到內(nèi)部主要是構(gòu)造函數(shù)push和析構(gòu)函數(shù)pop方法,內(nèi)部是由AutoreleasePoolPage管理揩抡。
objc_autoreleasePoolPush(void)
{
if (UseGC) return NULL;
return AutoreleasePoolPage::push();
}
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
if (!ctxt) return;
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePool自身沒(méi)有具體的結(jié)構(gòu)體數(shù)據(jù)户侥,真正管理對(duì)象指針的是AutoreleasePoolPage,整體的數(shù)據(jù)結(jié)構(gòu)是以AutoreleasePoolPage為單位的雙向鏈表峦嗤,AutoreleasePoolPage內(nèi)部結(jié)構(gòu)不細(xì)說(shuō)了蕊唐,除了保存一部分鏈表的信息數(shù)據(jù)外,其他剩余空間都是用來(lái)保存AutoreleasePool管理的對(duì)象指針烁设。
static inline void *push()
{
if (!hotPage()) {
# hotPage代表當(dāng)前的Page替梨,如果沒(méi)有會(huì)New一個(gè),設(shè)為當(dāng)前Page
setHotPage(new AutoreleasePoolPage(NULL));
}
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token) {
#mark 通過(guò)token獲取對(duì)應(yīng)的page
page = pageForPointer(token);
stop = (id *)token;
assert(*stop == POOL_SENTINEL);
} else {
// Token 0 is top-level pool
page = coldPage();
assert(page);
stop = page->begin();
}
#mark 釋放Page內(nèi)部指針直到碰到臨界值
page->releaseUntil(stop);
…………(太多了,省略一下)
這里的push是AutoReleasePool初始化第一次調(diào)用的時(shí)候副瀑,所以傳入的autoreleaseFast的參數(shù)是POOL_SENTINEL弓熏,POOL_SENTINEL是邊界對(duì)象,用于分隔的作用俗扇。
Pop內(nèi)部會(huì)獲取相應(yīng)的Page硝烂,然后調(diào)用releaseUntil方法,就開(kāi)始批量釋放內(nèi)部指針了铜幽。