1.1什么是自動引用計數(shù)
內(nèi)存管理中對引用采用自動計數(shù)的計數(shù)
在LLVM編譯器中設(shè)置ARC為有效狀態(tài), 就無需再次鍵入retain或者release代碼
1.2內(nèi)存管理/引用計數(shù)
1.2.1概要
引用計數(shù)例子: 開關(guān)燈
- 最早進(jìn)入辦公室的人開燈
- 之后進(jìn)入辦公室的人需要照明
- 下班早離開辦公室的人不需要照明
- 最后離開辦公室的人關(guān)燈
通過需要照明人數(shù)
來判斷是否有人在辦公室
狀態(tài) | 操作 | 分析 |
---|---|---|
第一個人進(jìn)入辦公室 |
需要照明人數(shù) +1 |
計數(shù)值從0變成1, 需要開燈 |
每當(dāng)有人進(jìn)入辦公室 |
需要照明人數(shù) +1 |
如計數(shù)值從1變成2 |
每當(dāng)有人離開辦公室 |
需要照明人數(shù) -1 |
如計數(shù)值從2變成1 |
最后的人離開辦公室 |
需要照明人數(shù) -1 |
計數(shù)值從1變成0, 需要關(guān)燈 |
- 照明設(shè)備 - 對象
- 人- 對象的使用環(huán)境
對照明設(shè)備所做的動作 | 對Objective-C對象所做的動作 |
---|---|
開燈 | 生成對象 |
需要照明 | 引用對象 |
不需要照明 | 釋放對象 |
關(guān)燈 | 回收對象 |
1.2.2內(nèi)存管理的思考方式
引用計數(shù)
比較客觀的思考方式:
- 自己生成的對象, 自己所持有
- 非自己生成的對象, 自己也能持有
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
對象操作與Objective-C方法對應(yīng)
對象操作 | Objective-C方法 |
---|---|
生成并且持有對象 | alloc/new/copy/mutableCopy等方法 |
持有對象 | retain方法 |
釋放對象 | release方法 |
廢棄對象 | dealloc方法 |
關(guān)于Objective-C內(nèi)存管理方法包含在Cocoa框架中, Cocoa框架中Foundation框架類庫的NSObject類負(fù)責(zé)內(nèi)存管理的職責(zé)
自己生成的對象, 自己所持有
alloc/new/copy/mutableCopy等方法生成的對象, 只有自己持有
// 以下方法, 所有objx對象引用計數(shù)器都會+1
id obj1 = [[NSObject alloc] init];
id obj2 = [[NSObject new];
id obj3 = [objc copy];
id obj4 = [objc mutableCopy];
非自己生成的對象, 自己也能持有
除alloc/new/copy/mutableCopy等方法之外的方法取得的對象, 因為非自己生成并持有, 所以自己不是該對象的持有者, 需要通過retain
方法來持有
// 取得非自己生成并持有的對象
id obj = [NSMutableArray array]; // 取得的對象存在, 但自己不持有對象
[obj retain]; // 通過-retian方法使自己持有對象
不再需要自己持有的對象時釋放
自己持有的對象一旦不再需要, 持有者有義務(wù)釋放該對象, 釋放使用release
方法
id obj1 = [[NSObject alloc] init]; // 自己生成并持有對象
[obj1 release]; // 釋放對象
id obj2 = [NSMutableArray array]; // 取得的對象存在, 但自己不持有對象
[obj2 retain]; // 通過-retian方法使自己持有對象
[obj2 release]; // 釋放對象
用alloc/new/copy/mutableCopy等方法生成并持有的對象, 或者使用retain方法持有的對象, 一旦不再需要, 務(wù)必要用release
方法進(jìn)行釋放
非自己持有的對象無法釋放
用alloc/new/copy/mutableCopy等方法生成并持有的對象, 或者使用retain方法持有的對象, 由于持有者是自己, 所以在不需要該對象時需要將其釋放. 除此之外得到的對象絕對不能釋放, 否則會造成崩潰
1.2.3 alloc/retain/release/dealloc實現(xiàn)
通過GNUstep來說明的
GNUstep是Cocoa框架的互換框架, 與Cocoa實現(xiàn)方式比較一致, 通過其來了解Cocoa框架的實現(xiàn)
1.2.4蘋果的實現(xiàn)
通過對alloc
類方法設(shè)置斷點追蹤程序的運行, 得出執(zhí)行所調(diào)用的方法和函數(shù)
+ alloc
+ allocWithZone:
class_createInstance
calloc
alloc
類方法首先調(diào)用allocWithZone:
類方法, 然后調(diào)用class_createInstance
函數(shù), 最后通過calloc
來分配內(nèi)存塊
retainCount/retain/release實例方法所調(diào)用的方法和函數(shù)
- retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
- retain
__CFDoExternRefOperation
CFBasicHashAddValue
- release
__CFDoExternRefOperation
CFBasicHashRemoveValue // 該方法返回0時, -realse調(diào)用dealloc
1.2.5autorelease
autorealse自動釋放, 更類似于C中的局部變量, 即超出作用域時對象實例的realease實例方法被調(diào)用
autorelease的具體使用方法:
- 生成并持有NSAutoreleasePool對象
- 調(diào)用已分配對象的autorelease實例方法
- 廢棄NSAutoreleasePool對象
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id objc = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; // 相當(dāng)于[obj release];
在Cocoa框架中, 相當(dāng)于程序主循環(huán)的NSRunLoop或者在其他程序可運行的地方, 對NSAutoreleasePool對象進(jìn)程生成, 持有和廢棄處理.
在大量產(chǎn)生autorelease對象時, 只要不廢棄NSAutoreleasePool對象, 那么生成的對象就不能被釋放, 有時會產(chǎn)生內(nèi)存不足的現(xiàn)象. 例如: 讀入大量圖像的同事改變其尺寸, 圖像文件讀入到NSData對象, 并從中生成UIImage對象, 改變對象尺寸后生成新的UIImage對象, 這種情況下會產(chǎn)生大量autorelease對象.
for (int i = 0; i < 圖像數(shù); ++i) {
// 讀入圖像
// 大量產(chǎn)生autorelease對象
// 由于沒有廢棄NSAutoreleasePool對象
// 最終導(dǎo)致內(nèi)存不足
[pool drain]; // 通過該方法, autorelease的對象被一起release
}
1.2.6autorelease實現(xiàn)
通過GNUstep, 我們能夠看到NSObject類的autorealse實例方法, 本質(zhì)就是調(diào)用NSAutoreleasePool對象的addObject類方法
- (id)autorelease {
[NSAutoreleasePool addobject: self];
}
1.2.7蘋果的實現(xiàn)
1.3ARC規(guī)則
1.3.1概要
引用計數(shù)式內(nèi)存管理
的本質(zhì)部分在ARC中并沒有改變, ARC只是自動地幫我們處理引用計數(shù)
的相關(guān)部分
- 一個應(yīng)用程序可以ARC和MRC混合使用
- 設(shè)置ARC有效的 編譯方法:
- 使用clang3.0及其以上版本
- 指定編譯器屬性為'-fobjc-arc'
1.3.2內(nèi)存管理的思考方式
- 自己生成的對象, 自己所持有
- 非自己生成的對象, 自己也能持有
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
這一思考模式在ARC時也是可行的, 只是在源碼的技術(shù)方法上稍有不同. 具體有什么變化需要先理解ARC中追加的所有權(quán)聲明
1.3.3所有權(quán)修飾符
Objective-C編程中為了處理對象, 可將變量類型定義為id類型或者各種對象類型
ARC有效時, id類型和對象類型同C語言其他類型不同, 其類型必須附加所有權(quán)修飾符, 所有權(quán)修飾符一共4種:
- __strong修飾符
- __weak修飾符
- __unsafe_unretained修飾符
- __autoreleasing修飾符
__strong修飾符
__strong修飾符是id類型和對象類型默認(rèn)的所有權(quán)修飾符
id obj = [[NSObject alloc] init];
// id和對象類型在沒有明確指定所有權(quán)修飾符時, 默認(rèn)為__strong修飾符
id __strong obj = [[NSObject alloc] init];
在MRC模式下, 改源碼可以記述為
{
id obj = [[NSObject alloc] init];
[obj release];
}
如上述代碼所示, 所有__strong
修飾符的變量obj在超出其變量作用域時, 即在該變量被廢棄時, 會釋放其被賦予的對象
如'strong'這個名詞所示, __strong
修飾符表示對對象的"強引用". 持有強引用的變量在超出其作用于時被廢棄, 隨著強引用的失效, 引用的對象隨之釋放
關(guān)注下源代碼中關(guān)于對象的所有者部分
{
// 自己生成并且持有對象
id __strong obj = [[NSObject alloc] init];
// 取得非自己生成并持有的對象
id __strong obj2 = [NSMutableArray array];
} /*
* 變量超出其作用于, 強引用失效
* 自動地釋放自己持有的對象
* 對象的所有者不存在, 因此廢棄該對象
*/
__strong
修飾符的變量, 不僅只在變量作用域中, 在復(fù)制上也能夠正確的管理其對象的所有者.
另外__strong
修飾符同之后的__weak
修飾符和__autoreleasing
修飾符一起, 可以保證將附有這些修飾符的自動變量初始化為nil
id __strong obj0;
id __weak obj1;
id __autoreleasing obj2;
// 下面代碼與以上相同
id __strong obj0 = nil;
id __weak obj1 = nil;
id __autoreleasing obj2 = nil;
__weak修飾符
如果僅僅使用__strong
修飾符, 無法解決 "循環(huán)引用" 的問題, 從而引發(fā)內(nèi)存泄露
- 所謂內(nèi)存泄漏就是應(yīng)當(dāng)廢棄的對象在超出其生命周期后繼續(xù)存在
__weak
修飾符與__strong
修飾符相反, 提供弱引用. 弱引用不能持有對象實例.
- 通過
__weak
修飾符從而解決循環(huán)引用的問題 -
__weak
修飾符還有另一優(yōu)點: 在持有某對象的弱引用時, 若該對象被廢棄, 則此弱引用將自動失效且處于nil被賦值的狀態(tài)
可以通過__weak
修飾符可避免循環(huán)引用, 通過檢查賦有__weak
修飾符的變量是否為nil, 可以判斷被復(fù)制的對象是否已被廢棄
__unsafe_unretained修飾符
__unsafe_unretained修飾符, 是不安全的所有權(quán)修飾符. 盡管ARC的內(nèi)存管理是編譯器工作, 但附有__unsafe_unretained
修飾符的變量不屬于編譯器的內(nèi)存管理對象
- 附有
__unsafe_unretained
修飾符的變量同附有__weak
修飾符的變量一樣, 不能持有對象. - 附有
__unsafe_unretained
修飾的變量如果被廢棄, 該引用不會被自動賦值為nil
在使用__unsafe_unretained
修飾符時, 賦值給附有__strong
修飾符的變量時, 有必要確認(rèn)被復(fù)制的對象確實存在
__autoreleasing修飾符
ARC有效時不能使用autorelease
方法, 也不能使用`NSAutoreleasePool'類. 雖然autorelease無法直接使用, 但是在arc有效時autorelease功能是起作用的
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
指定@autoreleasepool
快來替代NSAutoreleasePool
類對象生成, 持有以及廢棄這一范圍. 對象賦值給附有__autoreleasing
修飾符的變量, 等價于在MRC時調(diào)用對象的autorelease
方法, 即對象唄注冊到autoreleasepool
在訪問附有__weak
修飾符的變量時必須訪問注冊到autoreleasepool
的對象, 因為__weak
修飾符只有對象的弱引用, 而在訪問引用對象的過程中, 該對象有可能被廢棄, 如果要把訪問對象注冊到autoreleasepool
中, an么在@autore
快結(jié)束之前都能卻別該對象的存在
id的指針或?qū)ο蟮闹羔樤跊]有顯示指定時會被附加上__autoreleasing
修飾符
賦值給對象指針時, 所有權(quán)修飾符必須一致
1.3.4規(guī)則
在ARC有效的情況下編譯代碼, 必須遵守以下原則
- 不能使用 retain/release/retainCount/autorealease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必須遵守內(nèi)存管理方法的命名規(guī)則
- 不要顯式調(diào)用dealloc
- 使用@autoreleasepool快替代NSAutoreleasePool
- 不能使用區(qū)域(NSZone)
- 對象類型不能為C語言結(jié)構(gòu)體(struct/union)的成員
- 顯示轉(zhuǎn)換
id
和void *
不能使用 retain/release/retainCount/autorealease
編譯器自動使用了這些方法, 再次使用不符合內(nèi)存管理規(guī)范
在ARC有效時, 使用這些方法會編譯出錯
不能使用NSAllocateObject/NSDeallocateObject
一般通過NSObject類的alloc類方法來生成并持有Objective-C對象
在ARC有效時, 使用這些方法會編譯出錯
必須遵守內(nèi)存管理方法的命名規(guī)則
如1.2.2所述, 在ARC無效時, 用于對象生成/持有的方法必須遵守以下命名規(guī)則
- alloc
- new
- copy
- mutableCopy
以上名稱開始的方法, 在返回對象時, 必須返回給調(diào)用方法所應(yīng)當(dāng)持有的對象
在ARC有效時追加一條:
- init
以init開始的方法規(guī)則要比上述方法更嚴(yán)格, 該方法必須是實例方法, 并且必須返回對象, 返回的對象應(yīng)為id(instancetype)或該方法聲明類的對象類型, 亦或是該類的父類型或子類型, 該返回對象不注冊到autoreleasepool上, 基本知識對alloc方法返回的對象進(jìn)行初始化處理并返回該對象
不要顯式調(diào)用dealloc
無論ARC是否有效, 只要對象持有者不持有該對象, 對象就會被廢棄, 對象被廢棄時會自動調(diào)用該對象的dealloc方法
- 在ARC無效時,
dealloc
方法中必須調(diào)用[super dealloc]
- 在ARC有效時,
dealloc
方法中不能調(diào)用[super dealloc]
, 否則編譯出錯
使用@autoreleasepool快替代NSAutoreleasePool
在ARC有效時不能使用NSAutoreleasePool類, 參考1.3.3
不能使用區(qū)域(NSZone)
雖說ARC有效時不能使用區(qū)域(NSZone), 但是如1.2.3所屬, 不論ARC是否有效, 區(qū)域在現(xiàn)在的運行時系統(tǒng)中已被忽略
對象類型不能為C語言結(jié)構(gòu)體(struct/union)的成員
C語言結(jié)構(gòu)體中如果存在Objective-C對象類型變量, 會引起編譯錯誤
因ARC把內(nèi)存管理的工作分配給編譯器, 所以編譯器必須能夠知道并管理對象的生存周期
要把對象類型變量加入到結(jié)構(gòu)體成員中時 可強制轉(zhuǎn)換為void *或者附加__unsafe_unretained
修飾符, 如
// 注意, 附有__unsafe_unretained修飾符的變量不屬于編譯器的內(nèi)存管理對象, 如果使用時不注意賦值對象的所有者, 可能會遭遇內(nèi)存泄漏或程序崩潰
struct Data {
NSMutableArray __unsafe_unretained *array;
};
顯示轉(zhuǎn)換id
和void *
在ARC無效時, 以下代碼不會出錯
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];
但是在ARC有效時會引起編譯錯誤
id類型或?qū)ο箢愋妥兞抠x值給void *或者逆向賦值時都需要進(jìn)行特定的轉(zhuǎn)換, 如果只想單純的賦值, 則可進(jìn)行__bridge
轉(zhuǎn)換
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
但是轉(zhuǎn)換為void *的__bridge
轉(zhuǎn)換, 安全性甚至比__unsafe_unretained
更低, 如果使用不注意很可能引起程序崩潰
__bridge
還有另外兩種轉(zhuǎn)換
-
__bridge_retained
- 可使要轉(zhuǎn)換賦值的變量也持有所復(fù)制的對象
-
__bridge_transfer
- 被轉(zhuǎn)換的變量所持有的對象在該變量唄賦值給轉(zhuǎn)換目標(biāo)變量之后隨之釋放
通過以上兩種轉(zhuǎn)換, 不適用id類型或者對象類型變量也可以生成, 持有以及釋放對象. 雖然可以這樣做, 但是在ARC中并不推薦這種方法
// ARC有效
void *p = (__bridge_retained void *)[[NSObject alloc] init];
(void)(__bridge_gransfer id)p
// ARC無效
id p = [[NSObject alloc] init];
[p release];
這些轉(zhuǎn)換多數(shù)在使用Objective-C對象與Core Foundation對象之間的相互轉(zhuǎn)變中
這部分還講了CoreFoundation和Foundation對象之間的轉(zhuǎn)換及使用注意
1.3.5屬性
當(dāng)ARC有效時, Objective-C類的屬性也發(fā)生了變化, 以下可作為屬性聲明中的屬性來使用
屬性聲明的屬性 | 所有權(quán)修飾符 |
---|---|
assign |
__unsafe_unretained 修飾符 |
copy |
__strong 修飾符(但是賦值的是被復(fù)制的對象) |
retain |
__strong 修飾符 |
strong |
__strong 修飾符 |
unsafe_unretained |
__unsafe_unretained 修飾符 |
weak |
__weak 修飾符 |
1.3.6數(shù)組
__unsafe_unretain
修飾符以外的__strong
/__weak
/__autoreleasing
修飾符保證其指定的變量初始化為nil.
根據(jù)不同的目的選擇使用NSMutableArray/NSMutableDictionary/NSMutableSet等Foundation框架的容器, 這些容器會恰當(dāng)?shù)某钟凶芳拥膶ο蟛槲覀児芾磉@些對象
在__autoreleasing
修飾的情況下, 因為與設(shè)想的使用方式有差異, 最好不要使用動態(tài)數(shù)組, 由于__unsafe_unretained
修飾符在編譯器的內(nèi)存管理對象之外, 所以與void *類型一樣, 只能作為C語言的指針類型來使用
1.4ARC的實現(xiàn)
ARC是Objective-C運行時庫和編譯器進(jìn)行的內(nèi)存管理, ARC由一下工具, 類庫來實現(xiàn)
- clang(LLVM編譯器)3.0以上
- objc4 Objective-C運行時庫493.9以上
本節(jié)圍繞clang匯編輸出和objc4庫源碼進(jìn)行說明, 具體代碼詳見書(讀這章之前需要一些runtime基礎(chǔ))
1.4.1 __strong修飾符
賦值給附有__strong
修飾符的變量在實際使用中運行原理
這一塊比較繞, 可以先看代碼然后看后面的總結(jié), 最后再繞回來看方法下面的論述
alloc/new/copy/mutableCopy等方法
{
id __strong obj = [[NSObject alloc] init];
}
通過程序匯編輸出得到原源代碼
/* 編譯器的模擬代碼 */
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init);
objc_release(obj)
通過原源代碼所以, 兩次調(diào)用objc_msgSend方法(alloc和init), 變量作用域結(jié)束時通過objc_release釋放對象, 雖然ARC不能使用release方法, 但是由此可知, 編譯器自動插入了release.
其它構(gòu)造方法
{
id __srong obj = [NSMutableArray array];
}
編譯器原源代碼
id objc = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
最開始的array方法調(diào)用和最后變量作用域結(jié)束時的release與之前相同, 但是中間的objc_retainAutoreleasedReturnValue
函數(shù)是什么?
- 它是用于 自己持有(retain)對象 的函數(shù), 但它持有的對象應(yīng) 為返回
注冊在autoreleasepool中對象
的方法或是函數(shù)的返回值, 調(diào)用alloc/new/copy/mutableCopy
以外的方法, 由編譯器插入該函數(shù). - 這種方法是成對的, 與之相對的是
objc_autoreleaseReturnValue
, 它用于alloc/new/copy/mutableCopy
以外的類的構(gòu)造方法等用于返回的對象的實現(xiàn)上
+ (id)array {
return [[NSMutableArray Alloc] init];
}
轉(zhuǎn)換后的原源碼使用了objc_autoreleaseReturnValue
函數(shù)
+ (id)array {
id obj = id objc = objc_msgSend(NSMutableArray, @selector(array));
objc_msgSend(obj, @selector(init));
return objc_aotureleaseReturnValue(obj);
}
返回 注冊到autoreleasepool中對象 的方法使用了objc_autoreleaseReturnValue
函數(shù)返回 注冊到autoreleasepool中的對象, 但是objc_autoreleaseReturnValue
函數(shù)同objc_autorelease
函數(shù)不同, 一般不僅限于注冊對象到autoreleasepool中.
objc_autoreleaseReturnValue
函數(shù)會檢查使用該函數(shù)的方法/函數(shù) 調(diào)用方 的執(zhí)行命名列表, 如果方法/函數(shù)的 調(diào)用方 在調(diào)用了方法/函數(shù)后緊接著調(diào)用objc_retainAutoreleasedReturnValue
函數(shù), 那么就不將返回的對象注冊到autoreleasepool中, 而是直接傳遞到方法/函數(shù)的 調(diào)用方
objc_retainAutoreleasedReturnValue
函數(shù)與objc_retain
函數(shù)不同, 它即便不注冊到autoreleasepool中而返回對象, 也能夠正確的獲取對象
通過objc_retainAutoreleasedReturnValue
函數(shù)與objc_autoreleaseReturnValue
函數(shù)的寫作, 可以不將對象注冊到autoreleasepool中而直接傳遞, 這一過程達(dá)到了最優(yōu)化.
說白了, 就是
objc_retainAutoreleasedReturnValue
函數(shù)持有的對象應(yīng)是函數(shù)/方法的返回值, 這個返回值應(yīng)當(dāng)注冊在autoreleasepool中
objc_autoreleaseReturnValue
函數(shù)會判斷自己所在的這個函數(shù)/方法的調(diào)用方之后有沒有調(diào)用objc_retainAutoreleasedReturnValue
, 如果沒有, 就將返回值注冊到autoreleasepool中, 如果有就不注冊了, 而且它能讓objc_retainAutoreleasedReturnValue
函數(shù)正確的獲取到對象
1.4.2 __weak修飾符
延伸閱讀: weak的生命周期:具體實現(xiàn)方法
- 若附有
__weak
修飾符的變量所引用的對象被廢棄, 則將nil賦值給該變量 - 使用附有
__weak
修飾符的變量, 即是使用注冊到autoreleasepool中的對象
{
// 假設(shè)obj附加__strong修飾符且被賦值
id __weak obj1 = obj;
}
轉(zhuǎn)換后的原源代碼
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
objc_initWeak
函數(shù)內(nèi)部
obj1 = 0;
objc_storeWeak(&obj1, obj;
objc_destroyWeak
函數(shù)內(nèi)部