多線程與內(nèi)存管理

自動引用計數(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 則可以取消

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容