多線程與內存管理

自動引用計數

內存管理&引用計數

  • 自己生成的對象,自己所持有
  • 非自己生成的對象,自己也能持有
  • 無法釋放非自己持有的對象
  • 用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 則可以取消

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容