Effective Objective-C 2.0 Tips 總結 Chapter 5,6,7
Effective Objective-C 2.0 Tips 總結 Chapter 5,6,7
Chapter 5 記憶體管理
-
Tips 29 理解引用計數
- 引用計數是 Objective-C 記憶體管理的基礎,包括 ARC 也是建立在引用計數的基礎之上,理解引用計數機制,能更好的幫你使用 ARC
- 引用計數的基本原理是每個物件都有一個當前有多少物件希望他存活的計數器,當這個計數器歸零那麼這個物件就會被釋放
-
檢視引用計數的方法叫做
retainCount
但是實際並不建議使用這個方法除錯程式碼 -
如果物件 A 需要物件 B 存活,那麼 A 需要呼叫 B 的
retain
方法,如果物件 A 不再需要物件 B 存活了,那麼需要呼叫物件 B 的release
方法或者autorelease
方法 -
呼叫
release
並不會使物件被釋放,物件釋放被釋放取決於引用計數是否為 0 -
所有的物件最終都間接或直接的被一個根物件所引用,macOS 應用是
NSApplication
物件,iOS 則是UIApplication
物件,這兩個物件都是應用啟動時建立的單例 -
物件的
alloc
方法返回的物件由呼叫者持有 -
autorelease
並不會馬上減少物件的引用計數,而是在下一次 Event Loop(事件迴圈)時減少,以達到延遲釋放物件的效果 -
autorelease
通常用在函式返回物件的情況,保證物件跨越函式呼叫邊界
-
Tips 30 以 ARC 簡化引用計數
- ARC 只是自動為程式碼新增記憶體管理相關的程式碼
-
在 ARC 下,不允許呼叫
retain
,release
,autorelease
,dealloc
- ARC 呼叫這些方法時,不通過訊息派發機制,直接呼叫底層 C 語言版本,以提升效能
-
alloc
,new
,copy
,mutableCopy
命名開頭的方法,其返回的物件歸呼叫者所有 - ARC 能夠自動優化記憶體管理程式碼,減少不必要的記憶體管理操作
- ARC 能夠自動的幫我們解決大部分的記憶體管理問題,所以沒啥特別的要求,建議都使用 ARC
-
變數記憶體管理語義修飾符
-
__strong
:預設,強引用,表示需要保留這個值 -
__weak
:弱引用,表示不保留這個值,並且如果系統回收這個物件,那麼在獲取此變數的值的時候會的到nil
-
__unsafe_unretained
:不安全的引用,不保留此值,系統回收這個物件的時候,不會清空變數的值 -
__autoreleasing
:把物件“按引用傳遞”給方法時使用,表示此值在方法返回時自動釋放
-
- ARC 是通過在編譯時在我們的程式碼中插入對應的記憶體管理程式碼,並且只適用於 Objective-C 的程式碼,CoreFoundation 建立的物件,還是需要人工管理記憶體
-
Tips 31 在
dealloc
方法中只釋放引用,並解除監聽-
物件在被系統收回的時候會執行
dealloc
方法 -
dealloc
方法中需要做的事情:[super dealloc]
-
dealloc
方法中不適合做的事情:-
釋放開銷較大或系統內稀缺的資源(檔案描述符,套接字,大量記憶體等),因為
dealloc
方法並不會在特定時機呼叫,一般對於使用這樣資源的物件都需要提供名字類似open
和close
的方法處理申請和釋放資源的行為 - 執行非同步任務
-
釋放開銷較大或系統內稀缺的資源(檔案描述符,套接字,大量記憶體等),因為
-
物件在被系統收回的時候會執行
-
Tips 32 使用異常時留意記憶體管理問題
-fobjc-arc-exceptions
-
Tips 33 使用弱引用避免迴圈引用
- 發生迴圈引用最簡單的情況——兩個物件相互持有,複雜的情況是物件間的引用關係是閉環的
- 迴圈引用會導致記憶體洩漏
-
非 ARC 的情況下使用
assign
或者unsafe_unretained
來修飾弱引用屬性 -
ARC 的情況下使用
weak
來修飾弱引用的屬性,因為weak
的屬性在物件被釋放後會自動設定為 nil
-
Tips 34 使用
@autorelease
程式碼塊降低記憶體峰值@autorelease autorelease
-
Tips 35 使用 Xcode 提供的“殭屍物件(Zombie Object)”除錯記憶體管理問題
- 殭屍物件可以響應所有訊息,響應的方式是打贏一條包含訊息內容以及接受者的訊息,然後終止應用程式
- 殭屍物件用於除錯程式碼是否會使用到已經被銷燬的物件
-
Tips 36 不要使用
retainCount
retainCount retainCount
Chapter 6 Block & GCD
塊(block) 和 GCD 是蘋果多執行緒程式設計的核心,塊是一種可以用在 C,C++,Objective-C 嗲碼中的“詞法閉包”,使用 block 開發者可以把程式碼像物件一樣傳遞,讓程式碼在不同的上下文中(context)執行,並且塊可以訪問定義他範圍內的全部變數。
GCD 把執行緒抽象為“派發佇列(dispatch queue)”,開發者將塊放入佇列,由 GCD 負責處理所有排程事宜。
-
Tips 37 理解“塊”
-
塊通過
^
來定義 - 定義好的塊可以像函式一樣使用,也可以像變數一樣傳遞
- 塊可以捕獲在他被聲明範圍內所有的變數
-
塊捕獲的變數必須通過增加
__block
修飾符才能修改 - 塊會持有所捕獲的物件型別變數
-
定義在類例項方法中的塊還可以使用
self
變數,能夠修改例項變數,在宣告時無需加_block
,在塊內直接修改例項變數和使用self
來訪問變數是等效的,但是如果需要通過屬性來訪問例項變數,需要指明self
來使用屬性 -
塊中使用
self
有可能會導致迴圈引用 - 塊有可能分配在棧或堆上,也可以是全域性的,分配在棧上的塊可以拷貝到堆,這樣和普通的 Objective-C 物件一樣具備引用計數
-
塊通過
-
Tips 38 為常用的塊型別建立
typedef
typedef
-
Tips 39 使用 handle 塊降低程式碼分散程度
- 在建立物件時,使用 handle 塊將相關業務邏輯一起宣告,減少程式碼分散的情況
-
Tips 40 用塊引用其所屬物件是不要出現迴圈引用
- 使用塊時需要考慮迴圈引用的問題
- 設計 api 的時候,需要找到合適的機制解除迴圈引用,不能讓客戶端程式碼來處理
-
Tips 41 多用 GCD,少用同步鎖
-
使用
@synchronized
或者NSLock
效率不高,並且面對死鎖的情況,處理起來很麻煩 - 使用序列佇列(serial synchronization queue)可以保證程式碼順序執行,但是不能保證同步
- 使用柵欄塊(barrier block)可以保證資料同步處理
- 使用 GCD 比同步鎖效率要高
-
使用
-
Tips 42 多用 GCD,少用
performSelector
系列方法-
performSelector
系列方法的傳入引數和返回值型別都很侷限,並且一些情況下 ARC 無法自動新增記憶體釋放操作導致記憶體洩漏,並在編譯期間會提示警告-Warc-performSelector-leaks
- 最好的方法是將任務封裝到塊裡,然後使用 GCD 來執行
-
-
Tips 43 判斷什麼時候使用 GCD 什麼時候使用
NSOperationQueue
- 在需要純使用 Objective-C API 的時候
- 執行的任務需要取消的時候
- 操作間存在依賴關係的時候
- 需要使用 KVO 來觀察執行狀況的時候
- 需要指定操作優先順序的時候
- 需要重用的時候
-
Tips 44 通過 Dispatch Group 機制根據系統資源狀況來執行任務
- 一系列任務可以歸入到一個 dispatch group 中,在這組任務執行完後開發者會收到通知
- GCD 會根據系統資源來排程一個 dispatch group 中的任務
-
Tips 45 使用
diapatch_once
來執行只需執行一次的執行緒安全程式碼- 標記應該宣告在 static 或是 global 作用域中
- 可以實現單例
-
Tips 46 不要使用
dispatch_get_current_queue
- 會導致死鎖
- 函式行為不可預期
Chapter 7 系統框架
-
Tips 47 熟悉系統框架
- 系統框架的核心是 Foundation 和 CoreFoundation
- 音訊視訊,資料庫,網路都可以用系統框架來處理
- C 語言會為優秀的 Objective-C 開發者助力
-
Tips 48 多用塊列舉,少用 for 迴圈
- 使用“塊列舉”本身能通過 GCD 來併發的執行遍歷操作
- 如果知道 collection 中有什麼物件,那麼應該修改塊簽名指出物件型別
-
Tips 49 對自定義記憶體管理語義的 collection 使用無縫橋接(toll-free-bridging)
__bridge
-
Tips 50 構建快取時使用
NSCache
而非NSDictionary
NSCache NSPurgeableData
-
Tips 51 精簡
initialize
與load
的實現程式碼-
load
方法沒有複寫機制 -
各個類的
load
方法呼叫時機是不確定的,所以在load
方法中使用其他類是不安全的 -
首次使用某個類之前,系統會發送
initialize
訊息,initialize
訊息尊存複寫機制,所以要判斷當前需要初始化的是哪個類 -
load
和initialize
都應該精簡,這樣有助於提高應用的響應能力,也能減少迴圈引用的概率 -
無法在編譯期設定的全域性常量,可以放在
initialize
方法中初始化
-
-
Tips 52
NSTimer
會持有其目標物件NSTimer NSTimer