引用計(jì)數(shù)這個(gè)概念相當(dāng)容易理解(參見第29條)。需要執(zhí)行保留與釋放操作的地方也很容易就能看出來。所以Clang編譯器項(xiàng)目帶有一個(gè)"靜態(tài)分析器"(static analyzer)蝴乔,用于指明程序里引用計(jì)數(shù)出問題的地方径荔。舉個(gè)例子少辣,假設(shè)下面這段代碼采用手工方式管理引用計(jì)數(shù):
if ([self shouldLogMessage]) {
NSString *message = [[NSString alloc] initWithFormat:@"I am object, %p", self];
NSLog(@"message = %@", message);
}
此代碼有內(nèi)存泄漏問題耐齐,因?yàn)閕f語句塊末尾并未釋放message對象。由于在if語句之外無法引用message需频,所以此對象所占的內(nèi)存泄漏了丁眼。判定內(nèi)存是否泄漏所用的規(guī)則很簡明:調(diào)用NSString的alloc方法所返回的那個(gè)message對象的保留計(jì)數(shù)比期望值要多1.然而卻沒有與之對應(yīng)的釋放操作來抵消。因?yàn)檫@些規(guī)則很容易表述昭殉,所以計(jì)算機(jī)可以簡明地將其套用在程序上苞七,從而分析出有內(nèi)存泄漏問題的對象。這正是"靜態(tài)分析器"要做的事挪丢。
使用ARC時(shí)一定要記住蹂风,引用計(jì)數(shù)實(shí)際上還是要執(zhí)行的,只不過保留與釋放操作現(xiàn)在是由ARC自動(dòng)為你添加的乾蓬。稍后將會看到惠啄,除了為方法所返回的對象正確運(yùn)用內(nèi)存管理語義之外,ARC還有更多的功能任内。不過撵渡,ARC的那些功能都是基于核心的內(nèi)存管理語義而構(gòu)建的,這套標(biāo)準(zhǔn)語義貫穿于整個(gè)Objective-C語言死嗦。
由于ARC會自動(dòng)執(zhí)行retain趋距、release、autorelease等操作越除,所以直接在ARC下調(diào)用這些內(nèi)存管理方法是非法的节腐。具體來說,不能調(diào)用下列方法:
- retain
- release
- autorelease
- dealloc
直接調(diào)用上述任何方法都會產(chǎn)生編譯錯(cuò)誤摘盆,因?yàn)锳RC要分析何處應(yīng)該自動(dòng)調(diào)用內(nèi)存管理方法翼雀,所以如果手動(dòng)調(diào)用的話,就會干擾其工作骡澈。此時(shí)必須信賴ARC锅纺,令其幫你正確處理內(nèi)存管理事宜掷空,而這會使那些手動(dòng)管理引用計(jì)數(shù)的開發(fā)者不太放心肋殴。
實(shí)際上囤锉,ARC在調(diào)用這些方法時(shí),并不通過普通的Objective-C消息派發(fā)機(jī)制护锤,而是直接調(diào)用其底層的C語言版本官地。這樣做性能更好,因?yàn)楸A艏搬尫挪僮餍枰l繁執(zhí)行烙懦,所以直接調(diào)用底層函數(shù)能節(jié)省很多CPU周期驱入。比方說,ARC會調(diào)用與retain等價(jià)的底層函數(shù)objec_retain氯析。這也是不能覆寫retain亏较、release或autorelease的緣由,因?yàn)檫@些方法從來不會被直接調(diào)用掩缓。筆者在本節(jié)后面的文字中將用等價(jià)的Objective-C方法來指代與之相關(guān)的底層C語言版本雪情,這對于那些手動(dòng)管理過引用計(jì)數(shù)的開發(fā)者來說更易理解。
使用ARC時(shí)必須遵循的方法命名規(guī)則
將內(nèi)存管理語義在方法名中表示出來早已成為Objective-C的慣例你辣,而ARC則將之確立為硬性規(guī)定巡通。這些規(guī)則簡單地體現(xiàn)在方法名上。若方法名以下列詞語開頭舍哄,則其返回的對象歸調(diào)用者所有:
- alloc
- new
- copy
- mutableCopy
歸調(diào)用者所有的意思是: 調(diào)用上述四種方法的那段代碼要負(fù)責(zé)釋放方法所返回的對象宴凉。也就是說,這些對象的保留計(jì)數(shù)是正值表悬,而調(diào)用了這四種方法的那段代碼要將其中一次保留操作抵消掉弥锄。如果還有其他對象保留此對象,并對其調(diào)用了autorelease蟆沫,那么保留計(jì)數(shù)的值可能比1大叉讥,這也是retainCount方法不太有用的原因之一(參見第36條)。
若方法名不以上述四個(gè)詞語開頭饥追,則表示其所返回的對象并不歸調(diào)用者所有图仓。在這種情況下,返回的對象會自動(dòng)釋放但绕,所以其值在跨越方法調(diào)用邊界后依然有效救崔。要想使對象多存活一段時(shí)間,必須令調(diào)用者保留它才行捏顺。
維系這些規(guī)則所需的全部內(nèi)存管理事宜均由ARC自動(dòng)處理六孵,其中也包括在將要返回的對象上調(diào)用autorelease,下列代碼演示了ARC的用法:
- (EOCPerson*)newPerson {
EOCPerson *person = [[EOCPerson alloc] init];
return person;
/**
* The method name begins with `new’, and since `person’
* already has an unbalanced +1 reference count from the
* `alloc’, no retains, releases or autoreleases are
* required when returning.
*/
}
- (EOCPerson*)somePerson {
EOCPerson *person = [[EOCPerson alloc] init];
return person;
/**
* The method name does not begin with one of the "owning"
* prefixes, therefore ARC will add an autorelease when
* returning `person’.
* The equivalent manual reference counting statement is:
* return [person autorelease];
*/
}
- (void)doSomething {
EOCPerson *personOne = [self newPerson];
// …
EOCPerson *personTwo = [self somePerson];
// …
/**
* At this point, `personOne’ and `personTwo’ go out of
* scope, therefore ARC needs to clean them up as required.
* - `personOne’ was returned as owned by this block of
code, so it needs to be released.
* - `personTwo’ was returned not owned by this block of
code, so it does not need to be released.
* The equivalent manual reference counting cleanup code
* is:
* [personOne release];
*/
}
ARC通過命名約定將內(nèi)存管理規(guī)則標(biāo)準(zhǔn)化幅骄,初學(xué)此語言的人通常覺得這有些奇怪劫窒,其他編程語言很少像Objective-C這樣強(qiáng)調(diào)命名。但是拆座,想成為優(yōu)秀的Objective-C程序員就必須適應(yīng)這套理念主巍。在編碼過程中冠息,ARC能幫程序員做許多事情毛好。
除了會自動(dòng)調(diào)用"保留"與"釋放"方法外而柑,使用ARC還有其他好處刁憋,它可以執(zhí)行一些手動(dòng)操作很難甚至無法完成的優(yōu)化型宙。例如风钻,在編譯期贬派,ARC會把能夠互相抵消的retain俐芯、release篮绰、autorelease操作約簡肄渗。如果發(fā)現(xiàn)在同一個(gè)對象上執(zhí)行了多次"保留"與"釋放"操作镇眷,那么ARC有時(shí)可以成對地移除這兩個(gè)操作。
ARC也包含運(yùn)行期組件翎嫡。此時(shí)所執(zhí)行的優(yōu)化很有意義偏灿,大家看過之后就會明白為何以后的代碼都應(yīng)該用ARC來寫了。前面講到钝的,某些方法在返回對象前翁垂,為其執(zhí)行了autorelease操作,而調(diào)用方法的代碼可能需要將返回的對象保留硝桩,比如像下面這種情況就是如此:
// From a class where _myPerson is a strong instance variable
_myPerson = [EOCPerson personWithName:@"Bob Smith"];
調(diào)用"personWithName:"方法會返回新的EOCPerson對象沿猜,而此方法在返回對象之前,為其調(diào)用了autorelease方法碗脊。由于實(shí)例變量是個(gè)強(qiáng)引用啼肩,所以編譯器在設(shè)置其值的時(shí)候還需要執(zhí)行一次保留操作。因此衙伶,前面那段代碼與下面這段手工管理引用計(jì)數(shù)的代碼等效:
EOCPerson *tmp = [EOCPerson personWithName:@"Bob Smith"];
_myPerson = [tmp retain];
變量的內(nèi)存管理語義
ARC也會處理局部變量與實(shí)例變量的內(nèi)存管理祈坠。默認(rèn)情況下,每個(gè)變量都是指向?qū)ο蟮膹?qiáng)引用矢劲。一定要理解這個(gè)問題赦拘,尤其要注意實(shí)例變量的語義,因?yàn)閷τ谀承┐a來說芬沉,其語義和手動(dòng)管理引用計(jì)數(shù)時(shí)不同躺同。例如,有下面這段代碼:
@interface EOCClass : NSObject {
id _object;
}
@implementation EOCClass
- (void)setup {
_object = [EOCOtherClass new];
}
@end
在手動(dòng)管理引用計(jì)數(shù)時(shí)丸逸,實(shí)例變量_object并不會自動(dòng)保留其值蹋艺,而在ARC環(huán)境下則會這樣做。也就是說黄刚,若在ARC下編譯setup方法捎谨,則其代碼會變?yōu)?
- (void)setup {
id tmp = [EOCOtherClass new];
_object = [tmp retain];
[tmp release];
}
當(dāng)然,在此情況下,retain和release可以消去涛救。所以畏邢,ARC會將這兩個(gè)操作化簡掉,于是州叠,實(shí)際執(zhí)行的代碼還是和原來一樣。不過凶赁,在編寫設(shè)置方法(setter)時(shí)咧栗,使用ARC會簡單一些。如果不用ARC虱肄,那么需要像下面這樣來寫:
- (void)setObject:(id)object {
[_object release];
_object = [object retain];
}
但是這樣寫會出問題致板。假如新值和實(shí)例變量已有的值相同,會如何呢咏窿?如果只有當(dāng)前對象還在引用這個(gè)值斟或,那么設(shè)置方法中的釋放操作會使該值的保留計(jì)數(shù)降為0,從而導(dǎo)致系統(tǒng)將其回收集嵌。接下來再執(zhí)行保留操作萝挤,就會令應(yīng)用程序崩潰。使用ARC之后根欧,就不可能發(fā)生這種疏失了怜珍。在ARC環(huán)境下,與剛才等效的設(shè)置函數(shù)可以這么寫:
- (void)setObject:(id)object {
_object = object;
}
ARC會用一種安全的方式來設(shè)置: 先保留新值凤粗,再釋放舊值酥泛,最后設(shè)置實(shí)例變量。在手動(dòng)管理引用計(jì)數(shù)時(shí)嫌拣,你可能已經(jīng)明白這個(gè)問題了柔袁,所以應(yīng)該能正確編寫設(shè)置方法,不過用了ARC之后异逐,根本無須考慮這種"邊界情況"(edge case)捶索。
在應(yīng)用程序中,可用下列修飾符來改變局部變量與實(shí)例變量的語義:
- __strong: 默認(rèn)語義灰瞻,保留此值
- __unsafe_unretained: 不保留此值情组,這么做可能不安全,因?yàn)榈鹊皆俅问褂米兞繒r(shí)箩祥,其對象可能已經(jīng)回收了院崇。
- weak: 不保留此值,但是變量可以安全使用袍祖,因?yàn)槿绻到y(tǒng)把這個(gè)對象回收了底瓣,那么變量也會自動(dòng)清空。
- __autoreleasing: 把對象"按引用傳遞"(pass by reference)給方法時(shí),使用這個(gè)特殊的修飾符捐凭。此值在方法返回時(shí)自動(dòng)釋放拨扶。
比方說,想令實(shí)例變量的語義與不使用ARC時(shí)相同茁肠,可以運(yùn)用__weak 或__unsafe_unretained修飾符:
@interface EOCClass : NSObject {
id __weak _weakObject;
id __unsafe_unretained _unsafeUnretainedObject;
}
不論采用上面哪種寫法患民,在設(shè)置實(shí)例變量時(shí)都不會保留其值。
我們經(jīng)常會給局部變量加上修飾符垦梆,用以打破由"塊"(block匹颤, 參見第40條)所引入的"保留環(huán)"(retain cycle)。塊會自動(dòng)保留其所捕獲的全部對象托猩,而如果這其中有某個(gè)對象又保留了塊本身印蓖,那么就可能導(dǎo)致"保留環(huán)"【┬龋可以用__weak局部變量來打破這種"保留環(huán)":
NSURL *url = [NSURL URLWithString:@"http://www.example.com/"];
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
EOCNetworkFetcher * __weak weakFetcher = fetcher;
[fetcher startWithCompletion:^(BOOL success){
NSLog(@"Finished fetching from %@", weakFetcher.url);
}];
ARC如何清理實(shí)例變量
剛才說過赦肃,ARC也負(fù)責(zé)對實(shí)例變量進(jìn)行內(nèi)存管理。要管理好其內(nèi)存公浪,ARC就必須在"回收分配給對象的內(nèi)存(deallocate)"時(shí)生成必要的清理代碼(cleanup code)他宛。凡是具備強(qiáng)引用的變量,都必須釋放欠气,ARC會在dealloc方法中插入這些代碼堕汞。當(dāng)手動(dòng)管理引用計(jì)數(shù)時(shí),你可能會像下面這樣自己來編寫dealloc方法:
- (void)dealloc {
[_foo release];
[_bar release];
[super dealloc];
}
用了ARC之后晃琳,就不需要再編寫這種dealloc方法了讯检,因?yàn)锳RC會借用Objective-C++的一項(xiàng)特性來生成清理例程(cleanup routine)∥篮担回收Objective-C++對象時(shí)人灼,待回收的對象會調(diào)用所有C++對象的析構(gòu)函數(shù)(destructor)。編譯器如果發(fā)現(xiàn)某個(gè)對象里含有C++對象顾翼,就會生成名為.cxx_destruct的方法投放。而ARC則借助此特性,在該方法中生成清理內(nèi)存所需的代碼适贸。
要點(diǎn)
- 有ARC之后灸芳,程序員就無須擔(dān)心內(nèi)存管理問題了。使用ARC來編程拜姿,可省去類中的許多"樣板代碼"烙样。
- ARC管理對象生命期的辦法基本上就是:在合適的地方插入"保留"及"釋放"操作。在ARC環(huán)境下蕊肥,變量的內(nèi)存管理語義可以通過修飾符指明谒获,而原來則需要手動(dòng)執(zhí)行"保留"及"釋放"操作。
- 由方法所返回的對象,其內(nèi)存管理語義總是通過方法名來體現(xiàn)批狱。ARC將此確定為開發(fā)者必須遵守的規(guī)則裸准。
- ARC只負(fù)責(zé)管理Objective-C對象的內(nèi)存。尤其要注意: CoreFoundation對象不歸ARC管理赔硫,開發(fā)者必須適時(shí)調(diào)用CFRetain/CFRelease炒俱。