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