自動引用計數
ARC
自動引用計數 ARC :是指內存管理中對引用計數采取自動計數的計數。
蘋果文檔
ARC 是讓編譯器來進行內存管理。設置了 ARC 為有效狀態,就無需再次鍵入 retain 或者 release 代碼。降低了程序崩潰,內存泄漏等風險的風險的同時,很大程度減少了開發程序的工作量。編譯器完全清楚目標對象,并能立刻釋放那些不再使用的對象。
內存管理
- 自己生成的對象,自己所持有
- 非自己生成的對象,自己也可以持有
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
- autorelease 對象
對象操作 | Objective-C 方法 |
---|---|
生成并持有對象 | alloc/new/copy/mutableCopy等方法 |
持有對象 | retain |
釋放對象 | release |
廢棄對象 | dealloc |
自己生成的對象,自己所持有
下列名稱開頭的方法名意味著自己生成的對象自己所持有。
- alloc
- new
- copy
- mutableCopy
實例代碼
id obj = [NSObject new];
非自己生成的對象,自己也可以持有
用 alloc/new/copy/mutableCopy 以外的方法取得的對象,因為非自己生成并持有,所以不是該對象的持有者。但是我們可以持有它
實例代碼:
// 取得非自己生成的對象
id obj = [NSMutableArray array];
// 持有非自己生成的對象
[obj retain];
不再需要自己持有的對象時釋放
自己持有的對象,一旦不在需要,持有者有義務釋放該對象,釋放使用 release。
實例代碼:
// 自己生成并持有對象
id obj = [[NSObject alloc] init];
// 釋放對象
[obj release];
同理,用 alloc 方法由自己生成并持有的對象通過 release 方法就釋放了。非自己生成而持有的對象,若用 retain 方法變為自己持有,也可以使用 release 方法釋放。
// 取得非自己生成的對象
id obj = [NSMutableArray array];
// 持有對象
[obj retain];
// 釋放對象
[obj release];
非自己持有的對象無法釋放
釋放非自己持有的對象會造成崩潰
實例代碼:
// 自己生成并持有
id obj = [NSObject new];
// 釋放持有對象
[obj release];
// 釋放非自己持有的對象,因為之前已經釋放了。crash !!!
[obj release];
autorelease 對象
autorelease 對象可以達到對象存在但是自己又不持有的效果。如 [NSMutableArray array]
實現方案:
- (NSMutableArray *) array
{
// 創建并持有對象
id obj = [NSObject new];
// 取得對象存在,但是自己不持有
[obj autorelease];
return obj;
}
autorelease 對象會被 autoreleasepool 所持有,在 runloop 休眠的時候會釋放所持有的對象。
GUNstep 的計數實現
將引用計數存在對象占用的內存塊頭部的變量中。
- 在 Objective-C 的對象中存在引用計數這一個整數值。
- 調用 alloc 或是 retain 方法后,引用計數值加 1。
- 調用 release 后,引用計數減 1。
- 引用計數值為 0 時,調用 dealloc 方法廢棄對象。
蘋果的實現
蘋果使用了散列表來管理引用計數。
對比:
GUNstep
- 少量代碼即可完成。
- 能夠統一管理引用計數用內存塊與對象用內存快。
Apple
- 對象用內存快的分配無需考慮內存塊頭部。
- 引用計數表各記錄中存在內存塊地址,可以從各個記錄追溯到各對象內存塊。利于調試。
autorelease
autorelease 對象可以達到對象存在但是自己又不持有的效果。autorelease 對象離開作用域都將調用 release 實例方法。
具體使用(MRC):
- 生成并持有 NSAutoreleasePool 對象。
- 調用已分配對象的 autorelease 實例方法。
- 廢棄 NSAutoreleasePool 對象。
實例代碼:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [NSObject new];
[obj autorelease];
[pool drain];
ARC 規則
所有權修飾符
- __strong 修飾符
- __weak 修飾符
- __unsafe_unretained 修飾符
- __autorelease 修飾符
__strong 修飾符
__strong 是默認修飾符
__strong 修飾符表示對對象的強引用。持有強引用的變量在超出其作用域時被廢棄,隨著強引用失效,引用的對象會隨之釋放。
- 如果是自己生成的對象正常持有。
- 如果不是自己生成的對象,會自動加 retain 來持有。
__weak 修飾符
__strong 修飾符會造成循環引用問題,__weak 修飾符可以避免循環引用。__weak 不持有對象,在對象被釋放的時候會自動設置為 nil。
__weak 修飾符只能用于 iOS 5 以上以及 OS X Lion 以上版本,其他版本需要使用 __unsafe_unretained 來替代。
__unsafe_unretained 修飾符
__unsafe_unretained 修飾符是不安全的所有權修飾符。盡管 ARC 是由編譯器管理的,但是 __unsafe_unretained 修飾符修飾的變量不屬于編譯器的內存管理對象。
和 __weak 不持有對象一樣。釋放了不會被設置為 nil,會出現野指針的情況。
__autorelease 修飾符
在 ARC 下不能顯示的調用 autorelease 方法,那么需要使用 __autorelease 修飾。
實例代碼:
// MRC
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [NSObject new];
[obj autorelease];
[pool drain];
// ARC
@autoreleasepool{
id __autorelease obj = [[NSObject alloc] init];
}
知識點:
- 訪問 weak 變量是必須訪問注冊到 autorelease 的對象,因為 weak 修飾符只持有對象的弱引用,而在訪問過程中,該對象可能被廢棄。如果把要訪問的對象注冊到 autoreleasepool 中,那么在 runloop 周期內都不會給釋放。
- id 的指針和對象的指針(如
NSError **
)沒有顯示的修飾符那么默認是 __autorelease.
ARC 下編寫代碼規則
- 不能使用 retain / release / retainCount /autorelease。
- 不能使用 NSAllocateObject / NSDeallocateObject。
- 必須遵守內存管理的方法命名規則。
- 不要顯式的調用 delloc。
- 使用 @autoreleasepool 塊來替代 NSAutoreleasePool。
- 不能使用區域(NSZone)
- 對象型變量不能作為 C 語言結構體的成員。
- 顯示轉換 id 和 void *。
Toll-Free Bridge
Core Foundation 對象和 Objective-C 對象沒有區別,不同之處只是由哪個框架生成對象, Core Foundation 對象可以轉換成 Objective-C 對象來使用,轉換過程稱之為 Toll-Free Bridge
。有以下幾個關鍵字
- __bridge :只做類型轉換,但是不修改對象(內存)管理權。
- __bridge_retained:將Objective-C的對象轉換為Core Foundation的對象,同時將對象(內存)的管理權交給我們,后續需要使用CFRelease或者相關方法來釋放對象。
- __bridge_transfer:將Core Foundation的對象轉換為Objective-C的對象,同時將對象(內存)的管理權交給ARC。
ARC 實現
strong 修飾符
編譯器來管理,2 種情況
- alloc/new/copy/mutableCopy等方法生成的對象,會自動插入
release
方法。 - 非情況 1 方法生成的對象,如
[NSMutableArray array]
,會分別插入objc_autoreleaseReturnValue
和objc_retainAutoreleasedReturnValue
方法。
objc_autoreleaseReturnValue
和objc_retainAutoreleasedReturnValue
是成對出現的。在對象返回處調用objc_autoreleaseReturnValue
,并且強引用的時候緊接著會調用objc_retainAutoreleasedReturnValue
。
系統會對這個其中賦值過程進行優化。
weak 修飾符
weak 對象流程
- 初始化 weak:
objc_initWeak
- 改變 weak 值:
objc_storeWeak
- 釋放 weak 值:
objc_destroyWeak
Blocks
什么是 Blocks
Blocks 是 C 語言的擴充功能:帶有局部變量的匿名函數。
其他語言中的 Blocks 的名稱
語言 | 名稱 |
---|---|
C | Blocks |
Smalltalk | Blocks |
Ruby | Blocks |
LISP | Lambda |
Python | Lambda |
C++ | Lambda |
語法
^ 返回值類型 參數列表{
表達式
}
Blocks 變量
語法(用在屬性,變量處)
返回值類型 (^ 變量名)參數列表
截獲自動變量
- 普通變量獲取值得內容
- 指針變量強引用指針對象
__block
截獲的自動變量不能子啊 Blocks 塊內被修改,需要加 __block 修飾符。
Blocks 的實現
一個 Blocks 被編譯后會生成這么幾個結構體
- __block_impx:包含了 isa,標志位,保留字段,執行的函數指針。
- __xxx_block_desc_x:包含了保留字段,block 的大小。
-
xxx_block_impx_xxx
:包含了上面 2 個成員。
x 表示由編譯器決定的名字。
截獲自動變量的原理
截獲的變量,會在__block_impx
中生成相應的成員變量。普通變量值是拷貝值,指針則是強引用指針。
__block 修飾符
當用 __block 修飾符的時候,會多生成__Block_byref_val_x
,包含了被捕獲的值,isa,forwarding 指針等字段。
之所以可以在 block 內部修改 __block 變量因為生成這個結構體來包裝了一層,通過這個結構體達到修改外部變量的目的。
forwarding 指針
當一個棧 block 執行出了作用域的時候,其捕獲的 __block 變量也會出作用域,會出現野指針,如果把 __block 拷貝到堆上,這個時候這個 __block 變量也應該一同拷貝,需要有一個機制在被拷貝之后也會引用到系統的堆變量上。forwarding 指針就是做這個的,forwarding 在未被拷貝的時候始終指向自己,在拷貝之后 forwarding 指針會指向堆變量。防止了野指針的問題,也解決了拷貝之后的引用問題。
循環引用
在使用 block 由于捕獲變量都是強引用的,所以需要注意循環引用。可以使用 weak 變量來避免循環引用。
更詳細的內容可以看我另外一篇Block 博客
未完待續...