《Objective-C高級編程》自動引用計數 閱讀筆記系列
《Objective-C高級編程》自動引用計數 閱讀筆記 item1(內存管理/引用計數)
《Objective-C高級編程》自動引用計數 閱讀筆記 item2(ARC規則及其實現)
前言
放假前從圖書館借了幾本技術書,Objective-C高級編程是其中的一本,在豆瓣里評價挺高的,有8.2。雖然現在iOS的絕大多數項目基本上都是ARC的了,但是深入了解下蘋果的引用計數式內存管理的思考方式總是好的。認真地看完了第一章自動引用計數,像這種深入底層、源代碼講解知識點的方式很棒,但是這排版真的很糟糕,經常得對一個知識點看個好幾遍才弄懂在講什么。另外,中文博大精深,要是在閱讀的過程中,遇到長句時,如果沒正確斷好語句,可能句子的意思會千差萬別(ps:古時候,文言文似乎是沒有句號等符號的,感謝發明符號的人,要不然現在的中文更難理解千萬倍)。本著把書讀薄的精神,試著做了下閱讀筆記,大多數都是摘抄自書籍。
1.1 自動引用計數(ARC,Automatic Reference Counting)
概念:指內存管理中對引用采取自動計數的技術。
關鍵點:在LLVM編譯器中設置ARC為有效狀態,就無需再次鍵入retain或者是release代碼。
1.2 內存管理/引用計數
1.2.1 通過辦公室照明形象地解釋內存管理,
表1 對辦公室照明設備所做的動作和對Objective-C的對象所做的動作
對辦公室照明設備所做的動作 | 對Objective-C的對象所做的動作 |
---|---|
開燈 | 生成對象 |
需要照明 | 持有對象 |
不需要照明 | 釋放對象 |
關燈 | 廢棄對象 |
由此,可以推出引用計數的內存管理
1.2.2 內存管理的思考方式
- 自己生成的對象,自己所持有
- 非自己生成的對象,自己也能持有
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
表2 對象操作與Objective-C方法的對應
對象操作 | Objective-C方法 |
---|---|
生成并持有對象 | alloc/new/copy/mutableCopy等方法 |
持有對象 | retain方法 |
釋放對象 | release方法 |
廢棄對象 | dealloc方法 |
有關Objective-C內存管理的方法并不包含在Objective-C語言中,而是在包含在Cocoa框架中。如下:
*** 自己生成的對象,自己所持有 ***
使用以下名稱開頭的方法意味著自己生成的對象只有自己持有:
- alloc
- new
- copy
- mutableCopy
注意:下列幾個方法,并不是同一類別的方法:
- allocate
- newer
- copying
- mutableCopyed
*** 非自己生成的對象,自己也能持有 ***
用alloc/new/copy/mutableCoy以外的方法取得對象,因為非自己生成并持有,所以自己不是該對象的持有者。但是通過retain方法,非自己生成的對象跟用alloc/new/copy/mutableCoy方法生成并持有的對象一樣,成為了自己所持有的。
*** 不再需要自己持有的對象時釋放 ***
- 自己持有的對象,一旦不再需要,持有者有義務釋放該對象,務必使用release方法釋放。
- 使用autorelease方法,可以使取得的對象存在,但自己不持有對象
- 用來取得誰都不持有的對象的方法名不能以alloc/new/copy/mutableCopy開頭
- 通過retain方法也能將調用autorelease方法取得的對象變為自己持有
注意:autorelease的功能
*** 非自己持有的對象無法釋放 ***
釋放非自己持有的對象會造成程序崩潰。
相關案例:
- 自己生成并持有對象后,在釋放完不再需要的對象之后再次釋放
- 在“取得的對象存在,但自己不持有對象”時釋放
1.2.3 alloc/retain/release/dealloc實現
借助開源軟件GNUstep的源代碼中alloc/retain/release/dealloc的實現來理解蘋果的Cocoa實現。
總結如下:
- 在Objective-C的對象中存在引用計數這一整數值
- 調用alloc或是retain方法后,引用計數值加1
- 調用release方法后,引用計數值減1
- 引用計數值為0時,調用dealloc方法廢棄對象
1.2.4 蘋果的實現
由于NSObject類的源代碼沒有公開,利用Xcode的調試器(lldb)和iOS大概追溯內存管理和引用計數的實現。
蘋果的實現大概就是采用散列表(引用計數表)來管理引用計數。
如圖:
GNUstep將引用計數保存在對象占用內存塊頭部的變量中,而蘋果的實現,則是保存在引用計數表的記錄中。
通過內存卡頭部管理引用計數的好處如下:
- 少量代碼即可完成
- 能夠統一管理引用計數用內存塊與對象用內存塊
通過引用計數表管理引用計數的好處如下:
- 對象用內存塊的分配無需考慮內存塊頭部
- 引用計數表各記錄中存有內存塊地址,可從各個記錄中追溯到各對象的內存塊
*** 關于蘋果的實現的第2條特性在調試時有著舉足輕重的作用:***
-
即使出現故障導致對象占用的內存塊損壞,但只要引用計數表沒有被破壞,就能夠確認各內存塊的位置,如下:
Snip20160126_8.png - 在利用工具檢測內存泄露時,引用計數表的各記錄也有助于檢測各對象的持有者是否存在
1.2.5 autorelease
autorelease的作用:
當超出對象實例的作用域(相當于變量作用域)時,對象實例的release實例方法被調用。
autorelease的具體使用方法如下:
- 生成并持有NSAutoreleasePool對象
- 調用已分配對象的autorelease實例方法
- 廢棄NSAutoreleasePool對象
關鍵知識點:
- 在大量產生autorelease的對象時,只要不廢棄NSAutoreleasePool對象,那么生成的對象就不能被釋放,因此有時產生內存不足的現象。例如,讀入大量圖像的同時改變其尺寸。在這類情況下,有必要在適當的地方生成、持有或廢棄NSAutoreleasePool對象。
- Cocoa框架中有很多類方法用于返回autorelease的對象。例如NSMutableArray類的arrayWithCapacity類方法
1.2.6 autorelease的實現
為了理解autorelease的實現,同樣需要查看GNUstep的源代碼。
關鍵知識點:
- autorelease實例方法的本質就是調用NSAutoreleasePool對象的addObject類方法
- addObject類方法調用正在使用的NSAutoreleasePool對象的addObject實例方法
- 如果嵌套生成或持有的NSAutoreleasePool對象,理所當然會使用最內側的對象
- 如果調用NSObject類的autorelease實例方法,該對象將被追加到正在使用的NSAutoreleasePool對象中的數組中
- drain實例方法在廢棄autorelease對象數組之前,會先對數組中的所有對象調用release實例方法
1.2.7 蘋果的實現
通過objc4庫的runtime/objc-arr.mm可以確認蘋果的實現。
關鍵方法:
- void *objc_autoreleasePoolPush(void){}
- void objc_autoreleasePoolPop(void *ctxt){}
- void *objc_autorelease(id obj){}
另外,通過NSAutoreleasePool類中的調用用非公開類方法showPools(該類方法只能在iOS中使用)來確認已被autorelease的對象的狀況。調試輸出用非公開函數_objc_autoreleasePoolPrint()。該函數在檢查某對象是否被自動release時非常有用。