Objective-C高級編程:iOS與OS X多線程和內存管理

工具:利用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)結合引用計數表進行,


參考:塊替代傳統回調函數或delegate的意義



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的實現

注意:無論調用哪一個對象的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、CFGetRetainCountCFShow
屬性

屬性的特性修飾符必須和對應成員變量的所有權修飾符一致?。。?/p>

// weak和默認的__strong沖突了!!!
@property (nonatomic, weak) id obj;
數組

???必須將nil賦值給所有數組元素,使得元素所賦值對象的強引用失效,從而釋放那些對象;然后再使用free函數廢棄內存塊,否則會有內存泄漏?。。?/p>

ARC實現

__strong

賦值分為兩種情況:alloc/new/copy/mutableCopy系列和其他
涉及的函數有:objc_msgSend、objc_releaseobjc_retainobjc_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中修改自動變量的兩種方法:
      1. 靜態變量、靜態全局變量或全局變量
      2. __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變量的釋放

實質/本質

  • Block - 棧/堆/數據區上的 Block 的結構體實例,isa指針
  • __block 變量 - 棧上 __block 變量的結構體實例

Block超出變量作用域可存在的理由 => 將Block和__block變量從棧上復制到堆上解決
__block變量的結構體成員變量__fowarding存在的理由 => 實現無論__block變量配置在棧上還是堆上都能正確地進行訪問


Block循環引用
原因:Block中附有__strong修飾符的對象類型自動變量在從棧復制到堆上時,該對象會被Block所持有。
解決方案:

  1. ARC:通過 __weak__unsafe_unretained 修飾符(iOS4)來替代 __strong 類型的被截獲的自動變量
    通過 __block 說明符和設置nil來打破循環
  2. MRC:通過 __block 說明符指定變量不被Block所retain;ARC下__block說明符的作用僅限于使其能在Block中被賦值。
"原理"
如果對block做一次copy操作, block的內存就會在堆中
* 它會對所引用的對象做一次retain操作
* 非ARC : 如果所引用的對象用了__block修飾, 就不會做retain操作
* ARC : 如果所引用的對象用了__unsafe_unretained\__weak修飾, 就不會做retain操作

3. Grand Central Dispatch(GCD)

兩種Queue

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 Queue實現
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) - 啟動事件源監聽
GCD能夠調度的事件源分類
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容