《Objective-C高級編程-iOS與OS+X多線程和內存管理》讀書筆記

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

第一章:自動引用計數

  • 本身生成的對象,本身所持有。
  • 非本身生成的對象,本身也能持有
  • 再也不須要本身持有的對象時釋放
  • 非本身持有的對象沒法釋放
對象操做 Objective-C方法
生成並持有對象 alloc/new/copy/mutableCopy等
持有對象 retain
釋放對象 release
廢棄對象 dealloc

Cocoa 框架中 Foundation 框架類庫的 NSObject 類擔負內存管理的職責。編程

本身生成的對象,本身所持有

使用如下名稱開頭的方法名意味着本身生成的對象只有本身持有:數組

  • alloc
  • new
  • copy
  • mutableCopy

使用 NSObject 類的 alloc 類方法就能本身生成並持有對象。另外,使用 new 類方法也能生成並持有對象。[NSObject new][[NSObject alloc] init] 是徹底一致的bash

非本身生成的對象,本身也能持有

用上述方法以外的方法取得的對象即用 alloc/new/copy/mutableCopy 之外的方法取得的對象,由於非本身生成並持有,因此本身不是該對象的持有者。例如 NSArray 類的 array 類方法。經過 retain 方法,非本身生成的對象成爲了本身所持有的。多線程

再也不須要本身持有的對象時釋放

本身持有的對象,一旦再也不須要,持有者有義務釋放該對象,釋放使用的是 release 方法。 用 alloc/new/copy/mutableCopy 方法生成並持有的對象,或者用 retain 方法持有的對象,一旦再也不須要,務必要用 release 方法進行釋放併發

autorelease 提供這樣的功能,使對象再超出指定的生存範圍時可以自動並正確地釋放(調用release方法) app

Xnip2019-08-30_21-48-42

沒法釋放非本身持有的對象

對於用 alloc/new/copy/mutableCopy 方法生成並持有的對象,或者用 retain 方法持有的對象,因爲持有者是本身,因此在不須要該對象時須要將其釋放。而由此之外所獲得的對象絕對不能釋放。假若在應用程序中釋放了非本身所持有的對象就會形成崩潰。框架

  • Objective-C 的對象中存有引用技術這一整數值。
  • 調用 alloc 或是 retain 方法後,引用計數值加 1.
  • 調用 release 後,引用技術減 1.
  • 引用計數值爲 0 時,調用 dealloc 方法廢棄對象。

蘋果對引用計數大概是採用 散列表(引用計數表)來管理引用計數異步

GNUstep 將引用計數保存在對象佔用內存塊頭部的變量中,而蘋果則是保存在引用計數表的記錄中。async

經過內存塊頭部管理引用計數的好處:函數

  • 少許代碼便可完成
  • 可以統一管理引用計數用內存塊與對象用內存塊

經過引用計數表管理引用計數的好處:

  • 對象用內存塊的分配無序考慮內存塊頭部
  • 引用計數表各記錄中存有內存塊地址,可從各個記錄追溯到各對象的內存塊

在利用工具檢測內存泄露時,引用計數表的各記錄也有助於檢測各對象的持有者是否存在。

autorelease

autorelease 就是自動釋放,這看上去很像 ARC ,實際上更相似於 C 語言中自動變量(局部)變量的特性。 C 語言的自動變量特性:程序執行時,若某自動變量超出其做用域,該自動變量將被自動廢棄。 與 C 語言的自動變量不一樣的是,編程人員能夠設定變量的做用域。 autorelease 的具體使用方法以下:

  1. 生成並持有 NSAutoreleasePool 對象
  2. 調用已分配對象的 autorelease 實例方法
  3. 廢棄 NSAutoreleasePool 對象

Xnip2019-09-01_11-59-29

NSAutoreleasePool 對象的生存週期至關於 C 語言變量的做用域。對於全部調用過 autorelease 實例方法的對象,在廢棄NSAutoreleasePool 對象時,都將調用 release 實例方法。 在大量產生 autorelease 對象時,須要在適當的地方生成、持有或廢棄 NSAutoreleasePool 對象。 調用 NSObject 類的 autorelease 實例方法,該對象將被追加到正在使用的 NSAutoreleasePool 對象中的數組裏。即 本質就是調用 NSAutoreleasePool 對象的 addObject 類方法。

ARC 規則

循環引用容易發生內存泄露,所謂內存泄露就是應當廢棄的對象在超出其生存週期後繼續存在。使用__weak修飾符可避免循環引用,__weak修飾符與__strong修飾符 相反,提供弱引用。弱引用不能持有對象實例,由於__weak修飾符的變量不吃又對象,因此在超出其變量做用域時,對象即被釋放。 __weak修飾符還有另外一優勢:在持有對象的弱引用時,若該對象被被廢棄,則此弱引用將自動失效且處於 nil 被賦值的狀態。

在 ARC 有效的狀況下編譯源代碼需遵照以下規則:

  • 不能使用 retain/release/retainCount/autorelease
  • 不能使用 NSAllocateObject / NSDeallocateObject
  • 須遵照內存管理的方法命名規則
  • 不要顯式調用 dealloc
  • 使用 @autoreleasepoll 塊替代 NSAutoreleasePool
  • 不能使用區域 (NSZone)
  • 對象型變量不能做爲 C 語言結構體(struct/union)的成員
  • 顯式轉換 idvoid * , 經過 (__bridge void *)(__bridge id) 進行橋接

__weak 修飾符

weak 表與引用計數表相同,做爲散列表被實現。若是使用 weak 表,將廢棄對象的變量的地址做爲鍵值進行檢索,就能高速地獲取對應的附有 __weak 修飾符的變量的地址。另外,因爲一個對象可同時賦值給多個附有 __weak 修飾符的變量中,因此對於一個鍵值,可註冊多個變量的地址。

若是附有 __weak 修飾符的變量所引用的對象被廢棄,則將 nil 賦值給該變量的步驟:

  1. 從 weak 表中獲取廢棄對象的地址爲鍵值的記錄
  2. 將包含在記錄中的全部附有 __weak 修飾符的變量的地址,賦值爲 nil.
  3. 從 weak 表中刪除該記錄
  4. 從引用計數表中刪除廢棄對象的地址爲鍵值的記錄

若是大量使用附有 __weak 修飾符的變量,會消耗相應的 CPU 資源,良策是隻在須要避免循環引用時使用 __weak 修飾符。

第二章:Blocks

Blocks 是 C語言的擴充功能,用一句話表示:帶有自動變量(局部變量)的匿名函數。 Block 語法: ^ 返回值類型 參數列表 表達式

使用附有 __block 說明符修飾的自動變量可在 Block 中賦值,該變量成爲 __block 變量

在Block中,截獲自動變量的方法並無實現對C語言數組的截獲,這時使用指針能夠解決問題。

Blocks 的實現

Block 本質是一個 OC 對象,它內部也有一個 isa 指針。是封裝了函數調用和函數調用環境的 OC 對象。

iOS 類的本質即 class_t 結構體:

struct class_t {
   struct class_t *isa;
   struct class_t *superclass;
   Cache cache;
   IMP *vtable;
   uintptr_t data_NEVER_USE;
}
複製代碼

該實例名稱持有聲明的成員變量、方法的名稱、方法的實現(即函數指針)、屬性以及父類的指針。 Block 即爲 Objective-C 的對象。

Block 的三種類型

設置對象的存儲域 備註
_NSConcreteStackBlock 捕獲局部變量
_NSConcreteGlobalBlock 程序的數據區域(.data 區) 不捕獲自動變量
_NSConcreteMallocBlock 捕獲成員變量

Block 什麼時候會複製到堆

  • 調用 Block 的 copy 實例方法時
  • Block 做爲函數返回值返回時
  • 將 Block 賦值給附有 __strong 修飾符 id 類型的類或 Block 類型成員變量時
  • 在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中傳遞 Block 時。

第三章:Grand Central Dispatch

Grand Central Dispatch (GCD) 是異步執行任務的技術之一,通常將應用程序中記述的線程管理用的代碼在系統級中實現,開發者只須要定義想執行的任務並追加到適當的 Dispatch Queue 中,GCD 就能生成必要的線程並計劃執行任務。

多線程編程可能會出現的問題:

  • 多個線程更新相同的資源會致使數據的不一致(數據競爭)- 解決:使用 Serial Dispatch Queue (串行隊列)
  • 中止等待事件的線程會致使多個線程相互持續等待(死鎖)
  • 使用太多線程會消耗大量內存

Dispatch Queue 按照追加的順序(先進先出 FIFO)執行處理,另在執行處理時存在兩種 Dispatch Queue :一種是等待如今執行中處理的 Serial Dispatch Queue,另外一種是不等待如今執行中處理的 Concurrent Dispatch Queue。

GCD API

可以使用 dispatch_set_target_queue API 設置 Dispatch Queue 的優先級,同時也可使多個本應並行執行的多個 Serial Dispatch Queue,在目標 Serial Dispatch Queue 上串行執行。

dispatch_after 函數並不許時 由於 Main Dispatch Queue 在主線程的 Runloop 中執行,因此在好比每隔 1/60 秒自行的 Runloop 中,Block 最快3秒後自行,最慢在3秒 + 1/60 秒後執行,而且在 Main Dispatch Queue 有大量處理追加或主線程的處理自己有延遲時,這個時間會更長。

Dispatch Group 在追加到 Dispatch Queue 中的多個處理所有結束後想執行結束處理可以使用 Dispatch Group 實現。

dispatch_sync 如同簡易版的 dispatch_group_wait 函數,會在指定隊列中同步執行任務,在任務執行結束以前不會返回。若是在主線程同步執行 Block 就會出現死鎖。

dispatch_apply 函數可進行快速遍歷。因爲 dispatch_apply 函數與 dispatch_sync 函數相同,會等待處理執行結束,所以推薦在 dispatch_async 函數中非同步地執行 dispatch_apply 函數。

dispatch_suspend 函數可暫時掛起指定的 Dispatch Queue。 dispatch_resume 函數可恢復指定的 Dispatch Queue。

dispatch_semaphore 函數可對操做進行更細粒度的排他控制。 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) dispatch_semaphore_wait 函數等待 Dispatch Semaphore 的計數值達到大於或等於1. 該處理結束是使用 dispatch_semaphore_signal 函數將 Dispatch Semaphore 的計數值加1.

dispatch_once 函數是保證再應用程序執行中只執行一次指定處理的 API。

dispatch_IO 函數可多線程併發處理大文件,以提升文件讀取速度。

GCD 實現

Block 並非直接加入 FIFO 隊列中,而是先加入 Dispatch Continuation 這一 dispatch_continuation_t 類型結構體中,而後再加入 FIFO 隊列。用於記憶 Block 所屬的 Dispatch Group 喝其餘一些信息,一般叫上下文。

。。。

相關文章
相關標籤/搜索