自動引用計數
內存管理&引用計數
- 自己生成的對象,自己所持有
- 非自己生成的對象,自己也能持有
- 無法釋放非自己持有的對象
- 用alloc/new/copy/mutableCopy方法生成并持有的對象,或者用retain方法持有的對象,一旦不在需要,務必用release方法進行釋放
GNUstep中引用計數的實現
- 在OC的對象中存有引用計數這一整數值
- 調用alloc或者retain方法后,引進計數值加一
- 調用release后,引用計數減一
- 引用計數為0時,調用dealloc方法廢棄對象
蘋果中引用計數的實現
蘋果的實現與CNUstep類似,不同的是,GNUstep將引用計數保存在對象占用內存頭部的變量中,而蘋果的實現,則是保存在引用計數表的記錄中。
內存頭部管理VS引用計數表管理
內存頭部管理:
- 少量代碼即可完成
- 能夠統一管理引用計數內存塊與對象用計數塊
引用計數表管理:
- 對象用內存塊的分配無需考慮內存頭部
- 引用計數表各記錄中存有內存塊地址,可從各個記錄追溯到各對象的內存塊。這一點在調試的時候有重要意義,即使出現故障導致對象占用的內存塊損壞,可以通過引用計數表確認各內存塊的位置
autorelease
autorelease會像C語言的自動變量那樣來對待對象實例,當超出其作用域時,對象實例的release實例方法被調用。
具體使用方法:
1 生成并持有NSAutoreleasePool對象
2 調用已分配對象的autorelease實例方法
3 廢棄NSAutoreleasePool對象
調用NSObject類的autorelease實例方法,該對象將被追加到正在使用的NSAutoreleasePool對象中的數組里
通常在使用OC,也就是Foundation框架時,無論調用哪一個對象的autorelease方法,實現上的調用都是NSObject類的autorelease實例方法。但是對于NSAutoreleasePool類,autore實例方法已被該類重載,因此運行時就會出錯
-__
ARC規則
所有權修飾符
- _ _strong:表示對對象的強引用,持有強引用的變量在超出其作用域時被廢棄,隨著強引用的失效,引用的對象會隨之釋放
- __weak:在持有某對象的弱引用時,若該對象被廢棄,則此弱引用將自動失效且處于nil被被賦值狀態(空弱引用)
- __unsafe_unretained:同weak一樣,不能持有自己生成的對象,不安全所有權修飾符,不屬于編譯器的內存管理對象
- __autoreleasing:在ARC有效時,用@autorelease塊代替NSAutoreleasePool類,用附有__autoreleasing修飾符的變量替代autorelease方法。編譯器會檢查方法名是否以alloc/new/copy/mutablecopy開始,如果不是,則自動將返回值注冊到autoreleasePool中
為什么在訪問附有_ _weak修飾符的變量時必須訪問注冊到autoreleasePool的對象呢?
這是因為__weak修飾符只持有對象的弱引用, 而在訪問引用對象的過程中, 該對象有可能被廢棄. 如果要把訪問的對象注冊到 autoreleasePool 中, 那么@ autoreleasePool 塊結束之前都能確保該對象存在.
ps: NSObject **obj等效于NSObject * __autoreleasing *obj.
規則
- 不能使用 retain/release/retainCount/autorelease
- 不能使用 NSAllocateObject/NSDeallocateObject
- 須遵守內存管理的方法命名規則
- 不要顯示調用 dealloc
- 使用@ autoreleasePool 塊代替 NSAutoreleasePool
- 不能使用區域( NSZone)
- 對象型變量不能作為 C語言結構體(struct/union)的成員
- 顯示轉換 id 和 void*
- init
以 init 開始的方法的規則要比 alloc/new/copy/mutableCopy更嚴格.該方法必須是實例方法,并且必須返回對象, 返回的對象應為 id 類型或該方法聲明類的對象類型, 抑或是該類的超類型或子類型.該返回類型并不注冊到 autoreleasePool上,基本上只是對 alloc方法返回值的對象進行初始化處理并返回該對象
- 顯示轉換 id 和 void*
1 id 型或對象型變量賦值給 void* 或者逆向賦值時都需要進行特定的轉換, 如果只想單純地賦值, 則可以使用__bridge 轉換
2 __bridge_retained 轉換可使要轉換賦值的變量也持有所賦值的對象(類似于 retain), _ _bridge_transfer提供與此相反的動作,被轉換的變量所持有的對象在該變量被賦值給轉換目標后隨之釋放(類似于 release).
屬性
在 ARC 有效時:
assign --- __unsafe_unretained
copy --- __strong
retain --- __strong
strong --- __strong
unsafe_retain --- __ansafe_retain
weak --- __weak
ARC 的實現
__strong 的實現
- 通過objc_autoreleaseReturn 函數和 objc_retainAutoreleasedReturnValue 函數的協作, 可以不將對象注冊到 autoreleasePool 中而直接傳遞
__weak的實現
- objc_storeWea函數把第二參數的賦值對象的地址作為鍵值, 將第一參數的附有__ weak 修飾符的變量的地址注冊到 weak 表中. 如果第二個參數為0, 則把變量從 weak 表中刪除
- 對象被廢棄時,最后會調用 objc_clear_deallocating 函數:
(1)從 weak 表中獲取廢棄對象的地址為鍵值的記錄
(2)將包含在記錄中的所有附有__weak 修飾符的變量的地址, 賦值為 nil
(3)從 weak 表中刪除記錄
(4)從引用計數表中刪除廢棄對象的地址為鍵值的記錄 - 在使用__ weak修飾符變量的情形下, 增加了 objc_loadWeakRetained 函數和 objc-autorelease 函數的調用:
(1)objc_loadWeakRetained函數取出附有__ weak 修飾符變量所引用的對象并 retain
(2)objc-autorelease 函數將對象注冊到 autorelease 中 - 對于所有 allowsWeakReference 方法返回 NO 的類絕對不能使用__weak修飾符. 在使用__weak 修飾符的變量時, 當被賦值對象的 retainWeakReference 方法返回 NO 的情況下, 該變量將使用 nil
Blocks
blocks 概要
- 帶有自動變量(局部變量)的匿名函數
^ 返回值類型 參數列表 表達式
- 在 block 語法下, 可將 block 語法賦值給聲明為 block 類型的變量中
- block 類型變量可像 C語言中其他類型變量一樣使用(比如函數參數, 返回類型, 賦值等)
- 使用__ block說明符的自動變量可在 block 中賦值, 改變量稱為__block 變量, 對于 OC 對象來說, 截獲的對象不能賦值, 但是可以調用對象方法
- 在 block 中, 截獲自動變量的方法并沒有實現對 C語言數組的截獲, 使用指針代替
blocks 實現
- 通過 blocks 使用的匿名函數實際上被作為簡單的 C語言函數處理
- block 是__ main_block_impl_0結構體實例, 展開內容為:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 *Desc;
}
該結構體構造函數會這樣初始化:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
對于isa = &NSConcreteStackBlock的理解:
_NSConcreteStackBlock 相當于 class_t 結構體實例, 在將 block 作為 OC 對象處理時, 關于該類的信息放置于_ NSConcreteStackBlock 中.
- 各類的結構體就是基于 objc_class 結構體的 class_t結構體. 在 OC 中, class_t結構體實例, 生成并保持各個類的 class_ t 結構體實例, 該實例持有聲明的成員變量, 方法的名稱, 方法的實現(函數指針), 屬性以及父類的指針.
自動變量的截獲
- block 的自動變量截獲值針對 block 中使用的變量
- 所謂截獲自動變量值意味著在執行 block 語法時, block 語法表達式所使用的自動變量值被保存到 block 結構體實例中
__Block
- C語言中允許改寫 block 值的有:靜態變量, 靜態全局變量, 全局變量
- 在由 block 語法生成的值 block 上, 可以存在超過其變量作用域的被截獲對象的自動變量. 變量作用域結束的同時, 原來的自動變量被廢棄, 因此 block 中超過變量作用域而存在的變量如同靜態變量一樣, 將不能通過指針訪問原來的自動變量
- C語言有以下存儲域內說明符: typedef, extern, static, auto, register
- block 轉換為 block 的結構體類型的自動變量, __block 變量轉換為__block 變量的結構體類型的自動變量. 所謂結構體類型的自動變量, 即棧上生成的該結構體的實例
block 存儲域
- block 課設置在棧, 堆, 數據區, 分別對應_ NSConcreteStackBlock, _NSConcreteMallocBlock, _NSConcreteGlobalBlock
- 記述全局變量的地方有 block 語法時
- block 語法的表達式中不使用應截獲的自動變量時
在以上情況下, block 為_ NSConcreteGlobalBlock 類對象, 除此之外 Block 語法生成的為_ NSConcreteStackBlock 類對象, 設置在棧上 - 將 block 作為函數返回值返回時, 編譯器會自動生成復制到堆上的代碼
- 編譯器不能進行判斷的情況:
- 向方法或函數參數中傳遞 block 時
- 編譯器能進行判斷的情況:
- Cocoa 框架的方法且方法名中含有 usingBlock 等時
- GCD 的 API
__block 變量存儲域
- 若在1個 block 中使用__ block變量, 則當該 block 從棧復制到堆上時, 使用的所有__block 變量也必定配置在棧上. 這些__ block 變量也全部被復制到堆. 此時, block 持有__ block 變量.
- 棧上的__block 變量用結構體實例在__ block 變量從棧復制到堆上, 會將成員變量__forwarding 的值替換為復制目標堆上的__ block 變量用結構體實例的地址
截獲對象
- block 截獲對象后, 生成結構體成員變量copy 和 dispose, 并賦予相應的函數指針, 其調用時機為棧上的 block 復制到堆上時.
- _block_copy 函數被調用時, block 從棧復制到堆. 在釋放復制到堆上的 block 后, 誰都不持有 block 而調用 dispose 函數
- block 中使用對象類型的自動變量時, 除以下情形外, 推薦調用 block 的 copy 實例方法:
- block 作為函數返回值返回時
- 將 block 賦值給附有__ strong 修飾符的 id 類型或者 block 類型成員變量時
- 向方法名中含有 usingBlock 的 Cocoa 框架方法或者 GCD 中的 API 傳遞 block 時.
循環引用
避免循環引用的方法有__ weak 修飾符及__ unsafe_unretained 修飾符, 和__ block 修飾符
使用__block變量的優點:
通過__ block 變量可控制對象的持有期間
在不能使用__ weak 修飾符的環境中不使用__ unsafe_unretained 即可. 在執行 block 時可動態地決定是否將 nil 或其他對象賦值在__ block 變量中
使用__ block 變量的缺點
為避免循環引用必須執行 block
Grand Central Dispatch(GCD)
GCD 是異步執行任務的技術之一. 一般將應用程序中記述的線程管理用的代碼在系統級中實現. 開發者只需要定義想執行的任務并追加到適當的 Dispatch Queue 中, GCD 就能生成必要的線程并計劃執行任務. 由于線程管理是作為系統的一部分來實現的, 因此可統一管理, 也可執行任務 這樣就比以前的線程更有效率.
dispatch_queue_create
- 雖然串行隊列和并行隊列受到系統資源的限制, 但用 dispatch_queue_create 函數可以生成任意多個 Dispatch Queue
- 當生成多個串行隊列時, 各個串行隊列將并行執行. 雖然一個串行隊列中同時只能執行一個追加處理, 但如果將處理分別追加到多個串行隊列中, 各個串行隊列執行一個, 即為同時執行多個處理
- 一旦生成串行隊列并追加處理, 系統對于一個串行隊列就只會生成并使用一個線程
- 只在為了避免數據競爭時使用串行隊列
- 對于并行隊列來說, 不管生成多少, 由于 XNU 內核只使用有效管理的線程, 因此不會發生串行隊列的那些問題
-生成的 Dispatch Queue 必須由程序員釋放, 這是因為 Dispatch Queue 并沒有像 block 那樣具有作為 OC 對象來處理的技術 - 在 dispatch_async 函數中追加 block 到 Dispatch Queue 后, 即使立即釋放 Dispatch Queue, 該 Dispatch Queue 由于被 block 持有也不會被廢棄, 因而 block 能夠執行. block 執行結束后會釋放 Dispatch Queue, 這時誰都不持有 Dispatch Queue, 因此他會被廢棄
- 主線程只有1個, 因此 Main Dispatch Queue 是串行隊列
- 對于 Main Dispatch Queue 和 Global Dispatch Queue 執行 dispatch_retain函數和dispatch_release 函數不會引起任何變化, 也不會有任何問題
dispatch_set_target_queue
dispatch_set_target_queue可以變更 Dispatch Queue 的執行優先級
- 在必須將不可并行執行的處理追加到多個串行隊列中時, 如果使用dispatch_set_target_queue函數將目標指定為某一個串行隊列, 即可防止處理并行執行
- 將 Dispatch Queue 指定為dispatch_set_target_queue函數的參數, 不僅可以變更 Dispatch Queue 的執行優先級, 還可以作為 Dispatch Queue 的執行階層
dispatch_after
- dispatch_after 函數并不是在指定時間后執行處理, 而只是在指定時間追加處理到 Dispatch Queue
Dispatch Group
- Dispatch Group 在使用結束后需要通過 dispatch_release 函數釋放
- Dispatch Group 中也可以使用 dispatch_group_wait函數僅等待全部處理執行結束.
dispatch_barrier_async
- 使用dispatch_barrier_async函數和并行隊列可實現高效率的數據庫訪問和文件訪問
dispatch_apply
- dispatch_apply 函數會等待處理執行結束, 因此推薦異步調用 dispatch_apply 函數
Dispatch Semaphore
- 再沒有 Serial Dispatch 和 dispatch_barrier_async函數那么大粒度且一部分處理需要進行排他控制的情況下, 適合使用 Dispatch Semaphore
Dispatch Queue 沒有取消的概念, 一旦將處理追加到 Dispatch Queue 中, 就沒有方法可將該處理去除. Dispatch source 則可以取消