iOS 內存管理


內存管理

1.內存布局

alloc注冊流程
alloc會先調用_objc_rootAlloc()函數,_objc_rootAlloc()中會調用callAlloc函數,然后會調用_objc_rootAllocWithZone(),最終會執行到_class_createInstanceFromZone()中,主要的申請內存邏輯在這個函數中。
cls->instanceSize計算開辟內存空間大小
calloc開辟內存, 返回地址指針(obj)
obj->initInstanceIsa 類與isa指針關聯  
 
 
2.內存管理方案
TaggedPointer
 
  • 從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小對象存儲
  • 在沒有使用Tagged Pointer之前,NSNumber等對象需要動態分配內存、維護引用計數等,NSNumber指針存儲的是堆中NSNumber對象的地址值
  • 使用Tagged Pointer之后,NSNumber指針里面存儲的數據變成了:Tag + Data,也就是將數據直接存儲在了指針中,Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實際上它不再是一個對象了,它只是一個披着對象皮的普通變量而已。所以,它的內存並不存儲在堆中,也不需要malloc和free。
  • 在內存讀取上有着3倍的效率,創建時比以前快106倍。不但減少了64位機器下程序的內存占用,還提高了運行效率。完美地解決了小內存對象在存儲和訪問效率上的問題。
  • 這是一個特別的指針,不指向任何一個地址
  • 當指針不夠存儲數據時,才會使用動態分配內存的方式來存儲數據
 
Nonpointer_Isa 
 
 
nonpointer 標記是否是nonpointerisa 如果不是就單純的指向類 如果是就還有下邊的一些信息 0 是 純的isa。1 是nonpointerisa
has_assoc 是否有關聯對象
has_cxx_dtor 判斷是否有c++或者Objc的析構器。如果沒有可以快速釋放
shiftcls 開啟指針優化 存儲類指針
magic 用於調試判斷當前對象時真的對象還是沒有初始化的空間
weakly_referenced 標記當前對象是否被指向或者曾經指向一個弱引用對象 如果沒有可以盡快釋放
deallocating 是否正在釋放
has_sidetable_rc 標記當前有沒有sidetable 如果沒有可以更快的釋放
extra_rc 表示當前對象的引用計數 如果小於10 就存儲在這里 如果大於10 情況變得復雜 要往sideTable中放
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
 
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        uintptr_t nonpointer        : 1;  // 0:普通指針,1:優化過,使用位域存儲更多信息
    uintptr_t has_assoc         : 1;  // 對象是否含有或曾經含有關聯引用
    uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析構函數或OC的dealloc
    uintptr_t shiftcls          : 33; // 存放着 Class、Meta-Class 對象的內存地址信息
    uintptr_t magic             : 6;  // 用於在調試時分辨對象是否未完成初始化
    uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
    uintptr_t deallocating      : 1;  // 對象是否正在釋放
    uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 來存儲引用計數
    uintptr_t extra_rc          : 19;  // 引用計數能夠用 19 個二進制位存儲時,直接存儲在這里
    };
#endif
};
 
散列表:引用計數表與弱引用表
 
 
 
 
為什么不是一個table而是多個組成的tables?  成千上萬對象引用計數都在操作同一張表  存在效率問題
解決這個問題 引入一個分離鎖  多個表操作
 
怎么實現快速分流?
side tables的本質是一張Hash表
key(對象指針)    -(hash函數運算)> value(side tables)
 
提高效率
 
 
3.數據結構
 
spinlock_t  是忙等的鎖
適應於輕量訪問
循環等待詢問,不釋放當前資源
 
引用計數 hash表  為了提高查找效率  插入跟查找通過hash函數獲得的
 
 
weakly_referenced  :對象是否有弱引用  0沒有1有
Deallocating:當前對象是否正在alloc
Rc :對象實際引用計數值
 
弱引用表:
 
ARC:有編譯器和runtime共同協作完成引用計數
 
引用計數:
 
 
兩次hash表查詢  
+1操作   
 
 
 
 
 
4.dealloc實現:
 
 
 
 
object_dispose()實現
 
 
clearDeallocating() 實現
  • dealloc底層調用_objc_rootDealloc()
  • _objc_rootDealloc()調用objc_object::rootDealloc()
  • objc_object::rootDealloc()調用object_dispose()
  • object_dispose()進行了free(obj)釋放對象,同時調用objc_destructInstance()
  • 在objc_destructInstance()函數中判斷是否有析構函數和關聯引用,如果有,就要移除,最后調用clearDeallocating()
  • 在clearDeallocating()中進行引用計數refcnt的清除和weak指針的移除,並調用weak_clear_no_lock()(這個weak指針移除具體步驟在下面的weak指針清除的時候進行詳細分析。)
 
5.weak弱指針存儲的底層結構
 
 
  • Runtime全局維護了一個全局映射表StripedMap,根據對象的地址能夠獲取對應的散列表SideTable(注意!!!也有可能是多個對象共用一個散列表),散列表SideTable之中包含有weak表weak_table_t,weak_table_t中根據對象的地址能夠查到該對象對應的weak_entry_t實體,weak_entry_t用來管理對象的所有的weak指針,weak指針存儲在weak_referrer_t中。
  • 當我們在用__weak修飾對象的時候,運行時Runtime會在底層調用objc_initWeak()方法
  • objc_initWeak()方法會調用storeWeak();
  • storeWeak()這個函數會先判斷對象是否初始化,如果未初始化,則進行對象初始化,然后創建對應的SideTable;如果對象已經有SideTable,那么判斷weak指針是否需要更新,更新操作就是刪除對應location位置的weak_entry_t對象,創建新的weak_entry_t,然后插入到weak表weak_table_t中。
weak指針移除原理
1、移除時機:調用對象的dealloc方法時,中間會調用clearDeallocating,其中會調用weak_clear_no_lock對weak指針進行移除。
2、移除原理:weak_clear_no_lock底層會獲取weak表weak_table_t中的實體weak_entry_t,然后拿到其中的weak_referrer_t,拿到weak_referrer_t之后,遍歷並將其中的所有weak指針置為nil,最后把這個weak_entry_t從weak_table_t中移除。
3、weak指針本質:從源碼中可以看出weak指針的類型為是objc_object**,是對象的二維指針,就是指向對象地址的指針。
注冊函數weak_register_no_lock進行分析:如果一個對象正在進行dealloc的時候,進行weak指針的更新操作,就會直接crash!並報錯Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.
注冊時這么判斷作用就是:在對象正在釋放的過程中,或者對象已經釋放后,是不允許使用weak來引用實例變量的。這樣就是為了防止野指針的出現。
 
小插曲:
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM