iOS 內(nèi)存管理
-
iOS 內(nèi)存管理的理解?
實(shí)際上是三種方案的結(jié)合
1.1TaggedPointer
(針對(duì)類似于NSNumber
的小對(duì)象類型)
1.2NONPOINTER_ISA
(64位系統(tǒng)下)
- 第一位的 0 或 1 代表是純地址型
isa
指針叙身,還是NONPOINTER_ISA
指針低飒。 - 第二位,代表是否有關(guān)聯(lián)對(duì)象
- 第三位代表是否有
C++
代碼考润。 - 接下來(lái)33位代表指向的內(nèi)存地址
- 接下來(lái)有 弱引用 的標(biāo)記
- 接下來(lái)有是否
delloc
的標(biāo)記....等等
1.3 散列表(引用計(jì)數(shù)表、weak
表) -
SideTables
表在 非嵌入式的64位系統(tǒng)中,有 64張SideTable
表 - 每一張
SideTable
主要是由三部分組成。自旋鎖融涣、引用計(jì)數(shù)表、弱引用表精钮。 - 全局的 引用計(jì)數(shù) 之所以不存在同一張表中威鹿,是為了避免資源競(jìng)爭(zhēng),解決效率的問(wèn)題轨香。
- 引用計(jì)數(shù)表 中引入了 分離鎖的概念忽你,將一張表分拆成多個(gè)部分,對(duì)他們分別加鎖臂容,可以實(shí)現(xiàn)并發(fā)操作科雳,提升執(zhí)行效率
在 isa_t 聯(lián)合體中不僅僅表明了指向?qū)ο蟮牡刂沸畔ⅲ疫@個(gè) 64 位數(shù)據(jù)還記錄了其 bits 情況以及該實(shí)例每一位保存的對(duì)象信息
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
};
- 不能使用
retain
脓杉、release
糟秘、retainCount
、autorelease
球散。 - 不可以使用
NSAllocateObject
尿赚、NSDeallocateObject
。 - 必須遵守內(nèi)存管理方法的命名規(guī)則蕉堰。
- 不需要顯示的調(diào)用
Dealloc
凌净。 - 使用
@autoreleasePool
來(lái)代替NSAutoreleasePool
。 - 不可以使用區(qū)域
NSZone
屋讶。 - 對(duì)象性變量不可以作為
C
語(yǔ)言的結(jié)構(gòu)體成員冰寻。 - 顯示轉(zhuǎn)換
id
和void*
。
- 自己生成的對(duì)象性雄,自己持有
- 非自己生成的對(duì)象,自己可以持有
- 自己持有的對(duì)象不再需要時(shí)羹奉,需要對(duì)其進(jìn)行釋放
- 非自己持有的對(duì)象無(wú)法釋放
- 答案是肯定的迁筛,
_weak
修飾的變量屬于弱引用,如果沒(méi)有被注冊(cè)到@autoreleasePool
中耕挨,創(chuàng)建之后也就會(huì)隨之銷毀,為了延長(zhǎng)它的生命周期,必須注冊(cè)到@autoreleasePool
中肌幽,以延緩釋放。
- 存在64張哈希表中,根據(jù)哈希算法去查找所在的位置止邮,無(wú)需遍歷这橙,十分快捷
- 散列表(引用計(jì)數(shù)表、weak表) -
SideTables
表在 非嵌入式的64位系統(tǒng)中导披,有 64張SideTable
表 - 每一張SideTable
主要是由三部分組成屈扎。自旋鎖
、引用計(jì)數(shù)表
撩匕、弱引用表
鹰晨。 - 全局的引用計(jì)數(shù)
之所以不存在同一張表中,是為了避免資源競(jìng)爭(zhēng)止毕,解決效率的問(wèn)題模蜡。 -引用計(jì)數(shù)表
中引入了分離鎖
的概念,將一張表分拆成多個(gè)部分扁凛,對(duì)他們分別加鎖哩牍,可以實(shí)現(xiàn)并發(fā)操作,提升執(zhí)行效率
引用計(jì)數(shù)表(哈希表) - 通過(guò)指針的地址令漂,查找到引用計(jì)數(shù)的地址,大大提升查找效率
- 通過(guò)
DisguisedPtr(objc_object)
函數(shù)存儲(chǔ)丸边,同時(shí)也通過(guò)這個(gè)函數(shù)查找叠必,這樣就避免了循環(huán)遍歷。
struct SideTable {
// 保證原子操作的自旋鎖
spinlock_t slock;
// 引用計(jì)數(shù)的 hash 表
RefcountMap refcnts;
// weak 引用全局 hash 表
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
…..
}
}
- 簡(jiǎn)單說(shuō)是雙向鏈表纬朝,每張鏈表頭尾相接,有
parent
骄呼、child
指針 - 每創(chuàng)建一個(gè)池子共苛,會(huì)在首部創(chuàng)建一個(gè) 哨兵 對(duì)象,作為標(biāo)記
- 最外層池子的頂端會(huì)有一個(gè)
next
指針。當(dāng)鏈表容量滿了蜓萄,就會(huì)在鏈表的頂端隅茎,并指向下一張表
-
weak
修飾的指針變量嫉沽,在指向的內(nèi)存地址銷毀后辟犀,會(huì)在Runtime
的機(jī)制下,自動(dòng)置為nil
绸硕。 -
_Unsafe_Unretain
不會(huì)置為nil
堂竟,容易出現(xiàn)懸垂指針
魂毁,發(fā)生崩潰。但是_Unsafe_Unretain
比__weak
效率高出嘹。
- 避免內(nèi)存峰值,及時(shí)釋放不需要的內(nèi)存空間
- 用的弱引用 -
weak
表。也是一張 哈希表娶聘。 - 被
weak
修飾的指針變量所指向的地址是key
闻镶,所有指向這塊內(nèi)存地址的指針會(huì)被添加在一個(gè)數(shù)組里,這個(gè)數(shù)組是Value
丸升。當(dāng)內(nèi)存地址銷毀铆农,數(shù)組里的所有對(duì)象被置為nil
。
Strong
Strong
修飾符表示指向并持有該對(duì)象狡耻,其修飾對(duì)象的引用計(jì)數(shù)會(huì)加1墩剖。該對(duì)象只要引用計(jì)數(shù)不為0就不會(huì)被銷毀。當(dāng)然可以通過(guò)將變量強(qiáng)制賦值nil
來(lái)進(jìn)行銷毀夷狰。
Weak
weak
修飾符指向但是并不持有該對(duì)象岭皂,引用計(jì)數(shù)也不會(huì)加1。在Runtime
中對(duì)該屬性進(jìn)行了相關(guān)操作沼头,無(wú)需處理爷绘,可以自動(dòng)銷毀。weak
用來(lái)修飾對(duì)象进倍,多用于避免循環(huán)引用的地方土至。weak
不可以修飾基本數(shù)據(jù)類型。
assign
assign
主要用于修飾基本數(shù)據(jù)類型猾昆, 例如NSInteger
陶因,CGFloat
,存儲(chǔ)在棧中垂蜗,內(nèi)存不用程序員管理楷扬。assign
是可以修飾對(duì)象的,但是會(huì)出現(xiàn)問(wèn)題贴见。
copy
copy
關(guān)鍵字和strong
類似烘苹,copy
多用于修飾有可變類型的不可變對(duì)象上NSString
,NSArray
,NSDictionary
上。
__unsafe_unretain
__unsafe_unretain
類似于weak
片部,但是當(dāng)對(duì)象被釋放后螟加,指針已然保存著之前的地址,被釋放后的地址變?yōu)?僵尸對(duì)象
,訪問(wèn)被釋放的地址就會(huì)出問(wèn)題捆探,所以說(shuō)他是不安全的然爆。
__autoreleasing
將對(duì)象賦值給附有
__autoreleasing
修飾的變量等同于ARC
無(wú)效時(shí)調(diào)用對(duì)象的autorelease
方法,實(shí)質(zhì)就是扔進(jìn)了自動(dòng)釋放池。
- 根據(jù)代碼執(zhí)行的上下文語(yǔ)境黍图,在適當(dāng)?shù)奈恢貌迦?
retain
曾雕,release
- 主要是指
weak
關(guān)鍵字助被。weak
修飾的變量能夠在引用計(jì)數(shù)為0 時(shí)被自動(dòng)設(shè)置成 nil剖张,顯然是有運(yùn)行時(shí)邏輯在工作的。 - 為了保證向后兼容性揩环,
ARC
在運(yùn)行時(shí)檢測(cè)到類函數(shù)中的autorelease
后緊跟其后retain
搔弄,此時(shí)不直接調(diào)用對(duì)象的autorelease
方法,而是改為調(diào)用objc_autoreleaseReturnValue
丰滑。objc_autoreleaseReturnValue
會(huì)檢視當(dāng)前方法返回之后即將要執(zhí)行的那段代碼顾犹,若那段代碼要在返回對(duì)象上執(zhí)行retain
操作,則設(shè)置全局?jǐn)?shù)據(jù)結(jié)構(gòu)中的一個(gè)標(biāo)志位褒墨,而不執(zhí)行autorelease
操作炫刷,與之相似,如果方法返回了一個(gè)自動(dòng)釋放的對(duì)象郁妈,而調(diào)用方法的代碼要保留此對(duì)象浑玛,那么此時(shí)不直接執(zhí)行retain
,而是改為執(zhí)行objc_retainAoutoreleasedReturnValue
函數(shù)噩咪。此函數(shù)要檢測(cè)剛才提到的標(biāo)志位顾彰,若已經(jīng)置位,則不執(zhí)行retain
操作胃碾,設(shè)置并檢測(cè)標(biāo)志位拘央,要比調(diào)用autorelease
和retain
更快。
- 會(huì) 儒旬,為了延長(zhǎng)返回對(duì)象的生命周期,給其他使用者留足調(diào)用的時(shí)間
懸垂指針
指針指向的內(nèi)存已經(jīng)被釋放了栈源,但是指針還存在,這就是一個(gè)
懸垂指針
或者說(shuō)迷途指針
野指針
沒(méi)有進(jìn)行初始化的指針竖般,其實(shí)都是
野指針
MRC
@property (atomic,readWrite,retain) UIView *view;
ARC
@property (atomic,readWrite,strong) UIView *view;
如果改為基本數(shù)據(jù)類型,那就是 assign
。
- 棧區(qū)(
stack
):由編譯器自動(dòng)分配釋放 闭翩,存放函數(shù)的參數(shù)值,局部變量的值等迄埃。其 操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧疗韵。 - 堆區(qū)(
heap
):一般由程序員分配釋放, 若程序員不釋放侄非,程序結(jié)束時(shí)可能由OS回收 蕉汪。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表逞怨。 - 全局區(qū)(靜態(tài)區(qū))(
static
):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的者疤,初始化的 全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域叠赦。 - 程序結(jié)束后由系統(tǒng)釋放驹马。 - 文字常量區(qū):常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放眯搭。
- 程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼窥翩。
-
深拷貝
內(nèi)存拷貝淺拷貝
指針拷貝 - 集合類深拷貝通過(guò)歸檔鳞仙、解檔實(shí)現(xiàn)寇蚊。
- 訪問(wèn)了已經(jīng)被銷毀的內(nèi)存空間,就會(huì)報(bào)出這個(gè)錯(cuò)誤棍好。 根本原因是有
懸垂指針
沒(méi)有被釋放
-
@dynamic
意味著編譯器不會(huì)幫助我們自動(dòng)合成setter
和getter
方法。我們需要手動(dòng)實(shí)現(xiàn)借笙、這里就涉及到Runtime
的動(dòng)態(tài)添加方法的知識(shí)點(diǎn)扒怖。
App
啟動(dòng)后,蘋果在主線程RunLoop
里注冊(cè)了兩個(gè)Observer
业稼,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()
盗痒。第一個(gè)
Observer
監(jiān)視的事件是Entry
(即將進(jìn)入Loop
),其回調(diào)內(nèi)會(huì)調(diào)用_objc_autoreleasePoolPush()
創(chuàng)建自動(dòng)釋放池低散。其order
是-2147483647
俯邓,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前熔号。第二個(gè)
Observer
監(jiān)視了兩個(gè)事件:BeforeWaiting
(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
釋放舊的池并創(chuàng)建新池稽鞭;Exit
(即將退出Loop) 時(shí)調(diào)用_objc_autoreleasePoolPop()
來(lái)釋放自動(dòng)釋放池。這個(gè)Observer
的order
是2147483647
引镊,優(yōu)先級(jí)最低朦蕴,保證其釋放池子發(fā)生在其他所有回調(diào)之后篮条。在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)吩抓、
Timer
回調(diào)內(nèi)的涉茧。這些回調(diào)會(huì)被RunLoop
創(chuàng)建好的AutoreleasePool
環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏琴拧,開發(fā)者也不必顯示創(chuàng)建Pool
了
Retain
的實(shí)現(xiàn)機(jī)制蚓胸。
SideTable& table = SideTables()[This];
size_t& refcntStorage = table.refcnts[This];
refcntStorage += SIZE_TABLE_RC_ONE;
Release
的實(shí)現(xiàn)機(jī)制挣饥。
SideTable& table = SideTables()[This];
size_t& refcntStorage = table.refcnts[This];
refcntStorage -= SIZE_TABLE_RC_ONE;
二者的實(shí)現(xiàn)機(jī)制類似,概括講就是通過(guò)第一層 hash
算法沛膳,找到 指針變量
所對(duì)應(yīng)的 sideTable
扔枫。然后再通過(guò)一層 hash
算法,找到存儲(chǔ) 引用計(jì)數(shù)
的 size_t
锹安,然后對(duì)其進(jìn)行增減操作短荐。retainCount
不是固定的 1,SIZE_TABLE_RC_ONE
是一個(gè)宏定義叹哭,實(shí)際上是一個(gè)值為 4 的偏移量忍宋。
-
能不能簡(jiǎn)述一下
Dealloc
的實(shí)現(xiàn)機(jī)制?
Dealloc
的實(shí)現(xiàn)機(jī)制是內(nèi)容管理部分的重點(diǎn),把這個(gè)知識(shí)點(diǎn)弄明白风罩,對(duì)于全方位的理解內(nèi)存管理的只是很有必要糠排。
Dealloc
調(diào)用流程
- (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);
}
}
NONPointer_ISA
weakly_reference
has_assoc
has_cxx_dtor
has_sidetable_rc
- 如果有以上五中任意一種,將會(huì)調(diào)用
object_dispose()
方法超升,做下一步的處理入宦。 - 如果沒(méi)有之前五種情況的任意一種,則可以執(zhí)行釋放操作室琢,C函數(shù)的
free()
乾闰。
object_dispose()
調(diào)用流程。
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
- 直接調(diào)用
objc_destructInstance()
盈滴。 - 之后調(diào)用
C
函數(shù)的free()
涯肩。 - 執(zhí)行完畢
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;
}
- 先判斷
hasCxxDtor
,如果有C++
的相關(guān)內(nèi)容巢钓,要調(diào)用object_cxxDestruct()
病苗,銷毀C++
相關(guān)的內(nèi)容。 - 再判斷
hasAssocitatedObjects
竿报,如果有的話,要調(diào)用object_remove_associations()
继谚,銷毀關(guān)聯(lián)對(duì)象的一系列操作烈菌。 - 然后調(diào)用
clearDeallocating()
阵幸。 - 執(zhí)行完畢。
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());
}
void objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}
- 先執(zhí)行
sideTable_clearDellocating()
挚赊。 - 再執(zhí)行
weak_clear_no_lock
,在這一步驟中,會(huì)將指向該對(duì)象的弱引用指針置為nil
济瓢。 - 接下來(lái)執(zhí)行
table.refcnts.eraser()
荠割,從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)。 - 至此為止旺矾,
Dealloc
的執(zhí)行流程結(jié)束蔑鹦。
重寫setter
-(void)setBrand:(NSString *)brand{
//如果實(shí)例變量指向的地址和參數(shù)指向的地址不同
if (_brand != brand)
{
//將實(shí)例變量的引用計(jì)數(shù)減一
[_brand release];
//將參數(shù)變量的引用計(jì)數(shù)加一,并賦值給實(shí)例變量
_brand = [brand retain];
}
}
重寫getter
-(NSString *)brand{
//將實(shí)例變量的引用計(jì)數(shù)加1后,添加自動(dòng)減1
//作用,保證調(diào)用getter方法取值時(shí)可以取到值的同時(shí)在完全不需要使用后釋放
return [[_brand retain] autorelease];
}
重寫dealloc
//MRC下 手動(dòng)釋放內(nèi)存 可重寫dealloc但不要調(diào)用dealloc 會(huì)崩潰
-(void)dealloc{
[_string release];
//必須最后調(diào)用super dealloc
[super dealloc];
}
Memory Leaks
Allocations
Analyse
Debug Memory Graph
MLeaksFinder
泄露的內(nèi)存主要有以下兩種:
-
Leak Memory
這種是忘記 Release 操作所泄露的內(nèi)存。 -
Abandon Memory
這種是循環(huán)引用柬帕,無(wú)法釋放掉的內(nèi)存哟忍。
前四種都比較麻煩,需要不斷地調(diào)試運(yùn)行陷寝,第五種是騰訊閱讀團(tuán)隊(duì)出品锅很,效果好一些