工具:利用clang(LLVM編譯器)的命令:clang -rewrite-objc 源代碼文件名
將OC轉換成對應的C++源代碼。
另類總結:
四類關鍵字(
alloc/new/copy/mutableCopy等
| retain
| release
| dealloc
)四種所有權修飾符(
__strong
| __weak
| __unsafe_unretained
| __autoreleasing
)兩張散列表(引用計數表和weak表)+ 一個動態數組(
autoreleasepool
)+NSRunLoop屬性(
assign
| copy
| retain
| strong
| unsafe_unretained
| weak
)Toll-Free Bridge - 傳送門
思路串聯:
MRC下,內存需要人工管理,通過alloc等四類關鍵字(本質:calloc、free)結合引用計數表進行,
1. 自動引用計數
在LLVM【編譯器】中設置ARC為有效狀態,就無需鍵入retain或release代碼了,編譯器將結合【OC運行時】基于引用計數自動進行內存管理。
引用計數/內存管理
對照明設備所做的工作 | 對OC對象所做的動作 |
---|---|
開燈 | 生成對象 |
需要照明 | 持有 |
不需要照明 | 釋放 |
關燈 | 廢棄 |
內存管理的思考方式 | 對應OC方法 |
---|---|
自己生成的對象,自己所持有 | alloc/new/copy/mutableCopy等 |
非自己生成的對象(比如[NSArray array]),自己也能持有 | retain |
1. 不再需要自己持有的對象時釋放<br />2. 無妨釋放非自己持有的對象(比如多次release) | release |
當對象不被任何其他對象持有時廢棄 | dealloc |
蘋果的實現
=> alloc/retain/retainCount/release/dealloc實現
alloc => 調用class_createInstance
(calloc)分配內存 => 設置isa指針和成員變量初始值(0) => 在引用計數表中添加紀錄,并將引用計數值置為1
case OPERATION_retain:
CFBasicHashAddValue( table, obj );
return obj;
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey( table, obj );
return count;
case OPERATION_release:
count = CFBasicHashRemoveValue( table, obj );
return 0 == count;
dealloc => 刪除引用計數表中的對應記錄 => free內存塊
=> autorelease實現
autorelease方法的IMP Caching
注意:無論調用哪一個對象的autorelease實例方法,實際上調用的都是NSObject類的autorelease實例方法。(NSAutoreleasePool類的autorelease實例方法被重載了,運行時會報錯!!!)
ARC - 只是自動地幫助我們處理“引用計數”的相關部分。
文件的編譯屬性設置:-fobjc-arc
或 -fno-objc-arc
所有權修飾符
@autoreleasepool{}塊替代了NSAutoreleasePool類對象的生成持有和廢棄
__autoreleasing修飾符替代了autorelease方法的調用
autoreleasepool自動注冊
不以alloc/new/copy/mutableCopy開頭的方法(init系列方法除外)返回的對象將自動注冊
id的指針或對象的指針在沒有顯示指定時會被附加上__autoreleasing修飾符。
對象指針型賦值時,所有權修飾符必須一致。
id __autoreleasing *obj;
NSObject * __autoreleasing *obj;
拓展:附有__strong/__weak修飾符的變量類似于C++中的智能指針std::shared_ptr和std::weak_ptr。
ARC規則
- ……
- 須遵守內存管理方法的命名規則
- 以alloc/new/copy/mutableCopy名稱開頭的方法必須返回給調用方所應當持有的對象
- 以init開始的方法必須是返回類型為id/class/superclass/subclass的實例方法
- ……
- 顯示轉換id和void *
- CF對象與OC對象的轉換不需要使用額外的CPU資源,所以被稱為Toll-Free Bridge
// ARC: void *p = (__bridge_retained void *)obj;
CFTypeRef CFBridgeRetain(id X) {
return (__bridge_retained CFTypeRef)X;
}
// MRC
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
// ARC: id obj = (__bridge_transfer id)p;
id CFBridgeRelease(CFTypeRef X) {
return (__bridge_transfer id)X;
}
// MRC
id obj = (id)p;
[obj retain];
[(id)p release];
- CF還有以下方法:
CFRetain
、CFRelease
、CFGetRetainCount
和CFShow
屬性
屬性的特性修飾符必須和對應成員變量的所有權修飾符一致?。。?/p>
// weak和默認的__strong沖突了!!!
@property (nonatomic, weak) id obj;
數組
???必須將nil賦值給所有數組元素,使得元素所賦值對象的強引用失效,從而釋放那些對象;然后再使用free函數廢棄內存塊,否則會有內存泄漏?。。?/p>
ARC實現
__strong
賦值分為兩種情況:alloc/new/copy/mutableCopy系列和其他
涉及的函數有:objc_msgSend
、objc_release
和objc_retain
、objc_autorelease
__weak
修飾符功能:
- 若附有__weak修飾符的變量所引用的對象被廢棄,則將nil賦值給該變量;
- 對象廢棄時最后調用的objc_clear_deallocating函數的動作如下
1)從weak表中獲取廢棄對象的地址作為鍵值得記錄;
2)將包含在記錄中的所有附有__weak修飾符變量的地址,賦值為nil;
3)從weak表刪除該記錄;
4)從引用計數表中刪除廢棄對象的地址為鍵值的記錄。
使用附有__weak修飾符的變量,即是使用注冊到autoreleasepool中的對象;
- 源代碼解讀
1) objc_loadWeakRetained函數取出附有__weak修飾符的變量所引用的對象并retain;
2) objc_autorelease函數將對象注冊到autoreleasepool中。
- 最佳實踐:使用附有__weak修飾符的變量時,最好先暫時賦值給附有__strong修飾符的變量后再使用;從而避免對象多次注冊到autoreleasepool中。
不能使用__weak修飾符的情況
- iOS4及以下
- 通過
NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE
聲明了不支持的類,比如NSMachPort
- 以下方法返回NO的時候:
- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;
__autoreleasing修飾符
等同于ARC無效時調用對象的autorelease方法,即 objc_autorelease
方法的調用。
2. Blocks
-
語法:完整形式(
^T (…) { … }
)=> 基于推斷省略返回類型(^ (…) { … }
)=> 省略參數(^ { … }
)
很像函數指針, - 變量使用:使用typedef提高可讀性
- 截獲自動變量
- 賦值導致編譯錯誤(Mutable類的add方法不會!) => 解決方案:使用
__block
說明符 - 不能截獲C語言數組 => 解決方案:使用指針
實現
- 本質 OC對象(結構體)
-
isa 類結構指針
和 三大類型 _NSConcrete[Stack | Malloc | Global
]Block FuncPtr 函數指針
-
Desc
、Flags
和其他
-
- 截獲自動變量 - 只針對Block中使用的自動變量
-
__cself
和 OC中的self
、C++中的this
- 自動變量的值以成員變量的形式被保存到Block的結構體實例(或者說被其持有),通過
__cself
被使用;如果是__block變量,則轉化成結構體,其指針作為成員變量保存到Block結構體中 - 在Block中修改自動變量的兩種方法:
- 靜態變量、靜態全局變量或全局變量
-
__block
存儲域類說明符 - 類似于static、auto和register說明符,指定將變量值設置到哪個存儲域中。
-
三種類型
- _NSConcreteGlobalBlock - 存儲域:程序的數據區域
通過以下情況得到實例
1.記述全局變量的地方有Block語法時
2.Block語法的表達式中不使用截獲的自動變量時 - _NSConcreteStackBlock - 存儲域:棧;復制效果:到堆
除Global之外的Block語法生成的都是棧Block - _NSConcreteMallocBlock - 存儲域:堆;復制效果:引用計數增加
實際上當ARC有效時,多數情況編譯器會恰當地判斷,自動生成將Block從棧上復制到堆上的代碼!
什么時候棧上的Block會被復制到堆上呢?
1. 調用Block的copy實例方法時
2. Block作為函數返回值返回時
3. 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
4. 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時
需要手動復制的情形:NSArray的initWithObjects:(除4外作為方法參數時;注釋:現在應該連這個也OK了,請測試!?。。?;也即是ARC萬能。
實質/本質
-
Block
- 棧/堆/數據區上的 Block 的結構體實例,isa指針 -
__block
變量 - 棧上 __block 變量的結構體實例
Block超出變量作用域可存在的理由 => 將Block和__block變量從棧上復制到堆上解決
__block變量的結構體成員變量__fowarding存在的理由 => 實現無論__block變量配置在棧上還是堆上都能正確地進行訪問
Block循環引用
原因:Block中附有__strong修飾符的對象類型自動變量在從棧復制到堆上時,該對象會被Block所持有。
解決方案:
- ARC:通過
__weak
或__unsafe_unretained
修飾符(iOS4)來替代__strong
類型的被截獲的自動變量
通過__block
說明符和設置nil來打破循環 - MRC:通過
__block
說明符指定變量不被Block所retain;ARC下__block說明符的作用僅限于使其能在Block中被賦值。
"原理"
如果對block做一次copy操作, block的內存就會在堆中
* 它會對所引用的對象做一次retain操作
* 非ARC : 如果所引用的對象用了__block修飾, 就不會做retain操作
* ARC : 如果所引用的對象用了__unsafe_unretained\__weak修飾, 就不會做retain操作
3. Grand Central Dispatch(GCD)
GCD API
獲取系統提供的隊列:Main/Global Dispatch Queue;無需內存管理
使用 Concurrent Dispatch Queue 和 dispatch_barrier_async 函數可實現高效率的數據庫訪問和文件訪問。
GCD實現
GCD分為Dispatch Queue和Dispatch Source兩個部分,各自的實現如下:
Dispatch Queue
GCD是XNU內核級所實現的多線程管理API,根據CPU核等系統軟硬件情況進行了優化的線程池,提供高性能的簡單編程接口。
(注釋:Darwin - NeXT電腦公司開發的用于NEXTSTEP的XNU內核是兼有Mach3微內核和大量來自BSD宏內核的元素(進程、網絡、虛擬文件系統)以及I/O Kit的混合內核)
Dispatch Source
實現:BSD系內核慣有功能kqueue的包裝(XNU內核事件發生時,能在應用程序編程方執行處理)。
"使用慣例"
1. create or get - 獲取隊列
2. dispatch_source_create - 基于“監聽”的內核事件在隊列上構建Dispatch Source
3. dispatch_source_set_( timer|event_handler|cancel_handler ) - 配置Dispatch Source
一些列的處理方法,比如:dispatch_source_get_data、dispatch_source_cancel、dispatch_source_release
4. dispatch_resume(source) - 啟動事件源監聽