內(nèi)存布局
① 棧區(qū)
stack:方法調(diào)用會在棧區(qū)展開;
② 堆區(qū)heap:通過alloc分配的對象,copy后的block,都是在堆區(qū);
③ bss:為初始化的全局變量
④ data:已經(jīng)初始化的全局變量
⑤ text:程序的代碼段加載到內(nèi)存中時,都是在text段中的
內(nèi)存布局圖示
內(nèi)存管理方案
系統(tǒng)針對不同場景提供不同的內(nèi)存管理方案
① TaggedPointer:對于一些小對象如NSNumber等采用TaggedPointer管理方案;(深入理解Tagged Pointer)
② Nonpointer_isa:64位架構(gòu)(arm64)下應(yīng)用采用NONPOINTER_ISDA內(nèi)存管理方案。在64位架構(gòu)下,ISA指針占64bit位,實際上有30-40位就夠用了,剩下的位數(shù)就浪費了,Apple針對這種情況,為了提高內(nèi)存的利用率,在ISA當(dāng)中剩余的位中存放了一些內(nèi)存管理相關(guān)的內(nèi)容,所以這個叫做非指針型ISA(Nonpointer_isa);
③ 散列表:是一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu),其中包含了弱引用表和引用計數(shù)表
內(nèi)存數(shù)據(jù)結(jié)構(gòu)
- 散列表(side Tables()結(jié)構(gòu))
- side Tables()
實際是一張hash表,能通過hash算法快速查找某一個Side Table
side Tables()結(jié)構(gòu)圖示
為什么不是一個SideTable而是一個SideTables?
>為了提高訪問效率采用分離鎖的方式,將對象分配到不同的Side Table中,這樣對多個Side Table中的對象訪問可以同步訪問,如果只有一個Side Table,只能進行串行訪問,影響效率- Side Table:
spinlock_t涉及的內(nèi)容是一些多線程和資源競爭
side Table結(jié)構(gòu)圖示
① 自旋鎖spinlock_t:是"忙等"的鎖,適用于輕量訪問;
② 引用計數(shù)表RefcountMap:是一個hash表,將指針通過hash算法得到一個unsign long size_t,size_t的第一個表示是否有弱引用指針,第二位表示是否在執(zhí)行dealloc操作,所以size_t需要
右移兩位才能得到正確的引用計數(shù)值
size_t結(jié)構(gòu)
③ 弱引用表weak_table_t:是一個hash表
weak_table_t
ARC&MRC
- MRC手動引用計數(shù):
retain、release、retainCount、autorelease
- ARC 自動引用計數(shù):
① ARC 是LLVM編譯器(自動添加retain、release等代碼)與Runtime(weak表是通過運行時維護的,如weak對象被釋放時自動設(shè)置為nil)協(xié)作的結(jié)果;
在 runtime 源碼的 objc-internal.h 文件中聲明了一些內(nèi)存管理的函數(shù),代碼經(jīng)由編譯器編譯會添加這些函數(shù),從而實現(xiàn)引用計數(shù)的管理,包括 objc_alloc(Class cls)、objc_retain(id obj)、void objc_release(id obj) 等等,所以可以說 ARC 由編譯器和 runtime 協(xié)作完成
② ARC 中是禁止調(diào)用MRC中的獨有方法;
③ ARC 中新增weak、strong關(guān)鍵字
引用計數(shù)
① alloc:經(jīng)過一系列函數(shù)封裝和調(diào)用,最終調(diào)用了c函數(shù)calloc,此時并沒有設(shè)置引用計數(shù)為1;
② return:我們在return操作時,系統(tǒng)時如何查找對象的引用計數(shù)SideTable& table = SideTables()[this];//根據(jù)對象通過hash查找到對應(yīng)的SideTable size_t& refcntStorage = table.refcnts[this];//然后通過hash在refcnts引用計數(shù)表中查找引用計數(shù) refcbtStorage += SIDE_TABLE_RC_ONE;③ release:
SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); it->second -= SIDE_TABLE_RC_ONE;④ retainCount:只通過alloc創(chuàng)建的對象,調(diào)用retainCount獲取的值為1;
SideTable& table = SideTables()[this]; size_t refcnt_result = 1; RefcountMap::iterator it = table.refcnts.find(this); refcont_result += it->second >> SIDE_TABLE_RC_ONE;⑤ dealloc:
非指針型ISANonpointer_isa,弱引用weakly_referenced(如果有需要對弱引用對象進行清理),關(guān)聯(lián)對象assoc,C++、ARC(cxx_dtor),引用計數(shù)表sidetable_rc(是否使用了引用計數(shù)表管理引用計數(shù))
dealloc工作流程
rootDealloc實現(xiàn)相關(guān)inline void objc_object::rootDealloc() { assert(!UseGC); if (isTaggedPointer()) return; if (isa.indexed && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc) { assert(!sidetable_present()); free(this); } else { object_dispose((id)this); } }object_dispose實現(xiàn)相關(guān)
id object_dispose(id obj) { if (!obj) return nil; objc_destructInstance(obj); #if SUPPORT_GC if (UseGC) { auto_zone_retain(gc_zone, obj); // gc free expects rc==1 } #endif free(obj); return nil; }** objc_destructInstance**實現(xiàn)相關(guān)
void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = !UseGC && obj->hasAssociatedObjects(); bool dealloc = !UseGC; // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); if (dealloc) obj->clearDeallocating(); } return obj; }
弱引用
id __weak obj1 = obj; //經(jīng)過編譯后 id obj1; objc_initWeak(&obj1, obj);weak變量被廢棄后為什么會被置為nil:當(dāng)一個對象被dealloc后,dealloc會調(diào)用當(dāng)前對象的弱引用清除相關(guān)函數(shù)
weak_clear_no_lock(),在weak_clear_no_lock()內(nèi)部實現(xiàn)中會根據(jù)當(dāng)前函數(shù)指針查找弱引用表,把當(dāng)前對象中的弱引用都取出來,然后分別置為nil
自動釋放池
//編譯器會將@autoreleasepool{}改寫為 void *ctx = objc_autoreleasePoolPush(); {}中的代碼 objc_autoreleasePoolPop(ctx);自動釋放池的結(jié)構(gòu):
① 是以棧為節(jié)點通過雙向鏈表的形式組合而成的數(shù)據(jù)結(jié)構(gòu)。
② 是和線程一一對應(yīng)的。class AutoreleasePoolPage{ ... id *next;//指向棧當(dāng)中下一個可填充的位置 AutoreleasePoolPage * const parent;//父節(jié)點 AutoreleasePoolPage *child;//孩子節(jié)點 pthread_t const thread;//線程相關(guān) ... }在當(dāng)前runloop將要結(jié)束的時候,調(diào)用AutoreleasePoolPage::pop()。
autoreleasePool的實現(xiàn)原理:以棧為節(jié)點通過雙向鏈表的形式組合而成的一個數(shù)據(jù)結(jié)構(gòu)。
autoreleasePool為什么可以多層嵌套使用:多層嵌套就是在棧中多次插入AutoreleasePoolPage(哨兵)對象。
在什么樣的場景需要手動創(chuàng)建autoreleasePool:在進行內(nèi)存消耗較大的操作時,如在for循環(huán)中alloc圖片數(shù)據(jù)等需要手動插入autoreleasePool
循環(huán)引用
分類
@interface ClassA : NSObject @property (nonatomic, strong) id *obj; @end @interface ClassB : NSObject @property (nonatomic, strong) id *obj; @end自循環(huán)引用:自身持有自身
ClassA *a = [ClassA alloc] init]; a.obj = a;互相循環(huán)引用
ClassA *a = [ClassA alloc] init]; ClassB *b = [ClassB alloc] init]; a.obj = b.obj; b.obj = a.obj;多循環(huán)引用
即大環(huán)引用出現(xiàn)循環(huán)引用場景:代理、block、NSTimer、大環(huán)引用
如何破除循環(huán)引用:避免產(chǎn)生循環(huán)引用(weak,strong)、在合適的時機手動斷環(huán)
__weak, __block,
__block的使用問題
MRC 環(huán)境下,block 截獲外部用 __block 修飾的變量,不會增加對象的引用計數(shù)。
ARC 環(huán)境下,block 截獲外部用 __block 修飾的變量,會增加對象的引用計數(shù),無法避免循環(huán)引用,需要手動解環(huán)。
所以,在 MRC 環(huán)境下,可以通過 __block 來打破循環(huán)引用,在 ARC 環(huán)境下,則需要用 __weak 來打破循環(huán)引用。
NSTimer的循環(huán)引用問題
通過中間件(iOS中解決NSTimer循環(huán)引用問題)
iOS10中,定時器的API新增了block方法,可以使用新方法避免循環(huán)引用問題+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
總結(jié)
什么是ARC
為什么weak指針指向的對象在廢棄之后會被自動置為nil
蘋果是如何實現(xiàn)autoreleasePool的(原理)
什么是循環(huán)引用,你遇到過哪些循環(huán)引用,是怎么解決的