1、何檢測內(nèi)存泄漏
- Memory Leaks
- Alloctions
- Analyse
- Debug Memory Graph
- MLeaksFinder
泄露的內(nèi)存主要有以下兩種:
- Laek Memory 這種是忘記
Release
操作所泄露的內(nèi)存蚤霞。 - Abandon Memory 這種是
循環(huán)引用
挑豌,無法釋放掉的內(nèi)存安券。
2、循環(huán)引用
實質(zhì):多個對象相互之間有強引用浮毯,不能釋放讓系統(tǒng)回收完疫。
如何解決循環(huán)引用?
- 1债蓝、避免產(chǎn)生循環(huán)引用壳鹤,通常是將
strong
引用改為weak
引用。 - 在合適時機去手動斷開循環(huán)引用饰迹。
在
MRC
下芳誓,__block
不會增加其引用計數(shù)余舶,避免了循環(huán)引用
在ARC
下,__block
修飾對象會被強引用锹淌,無法避免循環(huán)引用匿值,需要手動解除。
NSTimer 循環(huán)引用屬于相互循環(huán)使用
創(chuàng)建 NSTimer
作為其屬性赂摆,由于定時器創(chuàng)建后也會強引用該控制器對象
挟憔,那么該對象和定時 器就相互循環(huán)引用了。
將定時器invalidate
并置為nil
即可
3烟号、懸垂指針绊谭?野指針?
懸垂指針:指針指向的內(nèi)存已經(jīng)被釋放了,但是指針還存在汪拥,這就是一個 懸垂指針 或者說 迷途指針
野指針: 沒有進行初始化的指針达传,其實都是 野指針
retain,copy,assign,weak,_Unsafe_Unretain
Strong
修飾符表示指向
并持有
該對象,其修飾對象的引用計數(shù)會加 1迫筑。該對象只要引用計數(shù)不為 0 就不會 被銷毀宪赶。當然可以通過將變量強制賦值 nil 來進行銷毀。
weak
修飾符指向
但是并不持有
該對象
脯燃,引用計數(shù)也不會加 1搂妻。在 Runtime 中對該屬性進行了相關(guān)操作, 無需處理曲伊,可以自動銷毀叽讳。weak 用來修飾對象追他,多用于避免循環(huán)引用的地方坟募。weak 不可以修飾基本數(shù)據(jù) 類型。
assign
主要用于修飾基本數(shù)據(jù)類型
邑狸, 例如 NSInteger懈糯,CGFloat,存儲在棧中单雾,內(nèi)存不用程序員管理赚哗。assign 是可以修飾對象的,但是會出現(xiàn)問 題硅堆。
copy
關(guān)鍵字和strong
類似屿储,copy 多用于修飾有可變類型的不可變對象 NSString,NSArray,NSDictionary 上。
__unsafe_unretain
類似于weak
渐逃,但是當對象被釋放后够掠,指針已然保存著之前的地址,被釋放后的地址 變?yōu)?僵尸對象茄菊,訪問被釋放的地址就會出問題疯潭,所以說他是不安全的赊堪。
__autoreleasing
將對象賦值給附有 __autoreleasing 修飾的變量等同于 ARC 無效時調(diào)用對象的 autorelease 方法,實質(zhì) 就是扔進了自動釋放池。
4竖哩、深拷貝 和 淺拷貝 集合類深拷貝如何實現(xiàn)
深拷貝: 該對象是否復(fù)制一份哭廉,內(nèi)容拷貝
淺拷貝: 指針拷貝,
對于集合對象的內(nèi)容復(fù)制僅僅是對對象本身相叁,但是對象的里面的元素還是指針復(fù)制遵绰。要想復(fù)制整個 集合對象,就要用集合深復(fù)制的方法增淹,有兩種:
(1)使用 initWithArray:copyItems:方法街立,將第二個參數(shù)設(shè)置為 YES 即可
NSDictionary * dct = [[NSDictionary alloc] initWithDictionary:dicto copyItems:YES]
(2)將集合對象進行歸檔(archive)然后解歸檔(unarchive):
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArr]]
5、Dealloc 的實現(xiàn)機制
一埠通、
Dealloc
調(diào)用流程
- 1.首先調(diào)用
_objc_rootDealloc()
- 2.接下來調(diào)用
rootDealloc()
- 3.這時候會判斷是否可以被釋放赎离,判斷的依據(jù)主要有 5 個,判斷是否有以上五種情況
- NONPointer_ISA
- weakly_reference
- has_assoc
- has_cxx_dtor
- has_sidetable_rc
- 4-1.如果有以上五中任意一種端辱,將會調(diào)用
object_dispose()
方法梁剔,做下一步的處理。 - 4-2.如果沒有之前五種情況的任意一種舞蔽,則可以執(zhí)行釋放操作荣病,C 函數(shù)的 free()。
- 5.執(zhí)行完畢渗柿。
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc(){
if (isTaggedPointer()) return; // fixme necessary?
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);
}
}
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
2.object_dispose() 調(diào)用流程个盆。
- 1.直接調(diào)用
objc_destructInstance()
。 - 2.之后調(diào)用 C 函數(shù)的
free()
朵栖。
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
3.objc_destructInstance() 調(diào)用流程
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.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
- 1.先判斷 hasCxxDtor颊亮,如果有 C++ 的相關(guān)內(nèi)容,要調(diào)用 object_cxxDestruct() 陨溅,銷毀 C++ 相關(guān)的內(nèi)容终惑。
- 2.再判斷 hasAssocitatedObjects,如果有的話门扇,要調(diào)用 object_remove_associations()雹有, 銷毀關(guān)聯(lián)對象的一系列操作。
- 3.然后調(diào)用 clearDeallocating()臼寄。
- 4.執(zhí)行完畢霸奕。
4.clearDeallocating() 調(diào)用流程。
inline void
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());
}
- 1.先執(zhí)行 sideTable_clearDellocating()吉拳。
- 2.再執(zhí)行 weak_clear_no_lock,在這一步驟中质帅,會將指向該對象的弱引用指針置為 nil。
- 3.接下來執(zhí)行 table.refcnts.eraser(),從引用計數(shù)表中擦除該對象的引用計數(shù)临梗。
- 4.至此為止涡扼,Dealloc 的執(zhí)行流程結(jié)束。
7 盟庞、內(nèi)存中的 5 大區(qū)
- 棧區(qū)(
stack
):由編譯器自動分配釋放 吃沪,存放函數(shù)的參數(shù)值,局部變量的值等什猖。其 操作方式類似于 數(shù)據(jù)結(jié)構(gòu)中的棧票彪。 -
堆區(qū)(heap)
:一般由程序員分配釋放, 若程序員不釋放不狮,程序結(jié)束時可能由 OS 回收 降铸。注意它與 數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表摇零。 -
全局區(qū)(靜態(tài)區(qū))(static)
:全局變量和靜態(tài)變量的存儲是放在一塊的推掸,初始化的 全局變量和靜態(tài) 變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域驻仅。 - 程序結(jié)束后 由系統(tǒng)釋放谅畅。 -
文字常量區(qū)
:常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放噪服。 -
程序代碼區(qū)
:存放函數(shù)體的二進制代碼毡泻。
8、內(nèi)存管理方案
-
taggedPointer
:存儲小對象如NSNumber
粘优。深入理解 Tagged Pointer -
NONPOINTER_ISA(非指針型的 isa)
:在 64 位架構(gòu)下仇味,isa 指針是占 64 比特位的,實際上只有 30 多位就 已經(jīng)夠用了雹顺,為了提高利用率丹墨,剩余的比特位存儲了內(nèi)存管理的相關(guān)數(shù)據(jù)內(nèi)容
散列表
第一位的 0 或 1 代表是純地址型 isa 指針,還是 NONPOINTER_ISA 指針无拗。- 第二位带到,代表是否有關(guān)聯(lián)對象
- 第三位代表是否有 C++ 代碼昧碉。
- 接下來 33 位代表指向的內(nèi)存地址
- 接下來有 弱引用 的標記
- 接下來有是否 delloc 的標記....等等
- 復(fù)雜的數(shù)據(jù)結(jié)構(gòu)英染,包括了引用計數(shù)表和弱引用表 通過 SideTables()結(jié)構(gòu)來實現(xiàn)的,SideTables()結(jié)構(gòu)下被饿,有很多 SideTable 的數(shù)據(jù)結(jié)構(gòu)四康。 而 sideTable 當中包含了自旋鎖,引用計數(shù)表狭握,弱引用表闪金。 SideTables()實際上是一個哈希表,通過對象的地址來計算該對象的引用計數(shù)在哪個 sideTable 中
SideTables
表在 非嵌入式的 64 位系統(tǒng)中,有 64 張 SideTable 表- 每一張
SideTable
主要是由三部分組成哎垦。自旋鎖囱嫩、引用計數(shù)表、弱引用表
漏设。- 全局的 引用計數(shù) 之所以不存在同一張表中墨闲,是為了避免資源競爭,解決效率的問題郑口。
- 引用計數(shù)表 中引入了 分離鎖的概念鸳碧,將一張表分拆成多個部分,對他們分別加鎖犬性,可以實現(xiàn)并發(fā)操 作瞻离,提升執(zhí)行效率
自旋鎖:
- 自旋鎖是“忙等”的鎖。
- 適用于輕量訪問乒裆。
引用計數(shù)表和弱引用表實際是一個哈希表套利,來提高查找效率。
9鹤耍、內(nèi)存布局
-
棧(stack)
:方法調(diào)用日裙,局部變量等,是連續(xù)的惰蜜,高地址往低地址擴展 -
堆(heap)
:通過 alloc 等分配的對象昂拂,是離散的,低地址往高地址擴展抛猖,需要我們手動控制 - 未初始化數(shù)據(jù)(
bss
):未初始化的全局變量等 - 已初始化數(shù)據(jù)(
data
):已初始化的全局變量等 -
代碼段(text)
:程序代碼
10格侯、@dynamic
@dynamic
意味著編譯器不會幫助我們自動合成setter
和getter
方法。我們需要手動實現(xiàn)财著、這里就涉及 到 Runtime 的動態(tài)添加方法的知識點联四。
11、@autoreleasePool 的數(shù)據(jù)結(jié)構(gòu)
- 簡單說是雙向鏈表撑教,每張鏈表頭尾相接朝墩,有 parent、child 指針 ;
- 每創(chuàng)建一個池子伟姐,會在首部創(chuàng)建一個
哨兵
對象,作為標記; - 最外層池子的頂端會有一個
next
指針收苏。當鏈表容量滿了,就會在鏈表的頂端愤兵,并指向下一張表鹿霸。
objc_autoreleasePoolPush
: 把當前 next 位置置為 nil,即哨兵對象,然后 next 指針指向下一個可入棧位置秆乳, AutoreleasePool
的多層嵌套懦鼠,即每次 objc_autoreleasePoolPush
钻哩,實際上是不斷地向棧中插入哨兵 對象。
objc_autoreleasePoolPop
: 根據(jù)傳入的哨兵對象找到對應(yīng)位置肛冶。 給上次 push 操作之后添加的對象依次發(fā)送release
消息街氢。 回退next
指針到正確的位置。
12睦袖、弱引用管理
- 添加
weak
變量:通過哈希算法位置查找添加阳仔。如果查找對應(yīng)位置中已經(jīng)有了當前對象所對應(yīng)的弱引用 數(shù)組,就把新的弱引用變量添加到數(shù)組當中扣泊;如果沒有近范,就創(chuàng)建一個弱引用數(shù)組,并將該弱引用變量 添加到該數(shù)組中延蟹。 ? 當一個被 weak 修飾的對象被釋放后评矩,weak 對象怎么處理的? 清除 weak 變量阱飘,同時設(shè)置指向為 nil斥杜。當對象被 dealloc 釋放后,在 dealloc 的內(nèi)部實現(xiàn)中沥匈,會調(diào)用弱 引用清除的相關(guān)函數(shù)蔗喂,會根據(jù)當前對象指針查找弱引用表,找到當前對象所對應(yīng)的弱引用數(shù)組高帖,將數(shù) 組中的所有弱引用指針都置為 nil缰儿。
__weak 屬性修飾的變量,如何實現(xiàn)在變量沒有強引用后自動置為 nil 散址?
用的弱引用 - weak
表乖阵。也是一張 哈希表。
被 weak
修飾的指針變量所指向的地址是 key 预麸,所有指向這塊內(nèi)存地址的指針會被添加在一個數(shù)組里瞪浸, 這個數(shù)組是 Value。當內(nèi)存地址銷毀吏祸,數(shù)組里的所有對象被置為 nil对蒲。
weak
修飾的指針變量,在指向的內(nèi)存地址銷毀后贡翘,會在 Runtime 的機制下蹈矮,自動置為nil
。