內(nèi)存管理相關(guān)

內(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)

  1. 散列表(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

  1. MRC手動引用計數(shù):

retain、release、retainCount、autorelease

  1. 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)引用,是怎么解決的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容