在對(duì)象之外訪問(wèn)實(shí)例變量時(shí),總是應(yīng)該通過(guò)屬性來(lái)做.然而在對(duì)象內(nèi)部訪問(wèn)實(shí)例變量時(shí),又該如何呢?這個(gè)問(wèn)題在OC的開(kāi)發(fā)中一直存在爭(zhēng)論,筆者認(rèn)為:
除了幾種特殊情況之外,強(qiáng)烈建議大家在讀取實(shí)例變量時(shí)采用直接訪問(wèn)的形式,而在設(shè)置實(shí)例變量的時(shí)候通過(guò)屬性來(lái)做.
請(qǐng)看線面這個(gè)類:
@interface CWGPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
- (NSString *)fullName;
- (void)setFullName:(NSString *)fullName;
@end
fullName與setFullName這兩個(gè)"便捷方法"可以這樣來(lái)實(shí)現(xiàn):
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@, %@", self.firstName, self.lastName];
}
- (void)setFullName:(NSString *)fullName {
NSArray *components = [fullName componentsSeparateByString:@" "];
self.firstName = components[0];
self.lastName = components[1];
}
在fullName的獲取方法中設(shè)置方法中, 我們使用"點(diǎn)語(yǔ)法", 通過(guò)存取方法來(lái)訪問(wèn)相關(guān)實(shí)例變量.現(xiàn)在假設(shè)重寫(xiě)這兩個(gè)方法, 不經(jīng)過(guò)存取方法,而是直接訪問(wèn)實(shí)例變量:
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@, %@", _firstName, _lastName];
}
- (void)setFullName:(NSString *)fullName {
NSArray *components = [fullName componentsSeparateByString:@" "];
_firstName = components[0];
_lastName = components[1];
}
這兩種寫(xiě)法有幾個(gè)區(qū)別:
- 由于不經(jīng)過(guò)OC的"方法派發(fā)"步驟,所以直接訪問(wèn)實(shí)例變量的速度當(dāng)然快,在這樣情況下,編譯器所生成的代碼會(huì)直接訪問(wèn)班車(chē)對(duì)象實(shí)例變量的那塊內(nèi)存.
- 直接訪問(wèn)實(shí)例變量時(shí),不會(huì)調(diào)用"設(shè)置方法",這就繞過(guò)了為相關(guān)屬相所定義的"內(nèi)存管理語(yǔ)義".比如說(shuō):如果在ARC下直接訪問(wèn)一個(gè)聲明為copy的屬性,那么并不會(huì)拷貝該屬性,只會(huì)保留新值并釋放舊值.
- 如果直接訪問(wèn)實(shí)例變量,那么不會(huì)觸發(fā)"鍵值觀察者(KVO)通知",這樣做是否會(huì)產(chǎn)生問(wèn)題,還取決于具體對(duì)象行為.
- 通過(guò)屬性來(lái)訪問(wèn)有助于排查與之相關(guān)的錯(cuò)誤,因?yàn)榭梢栽?獲取方法"和"設(shè)置方法"中加斷點(diǎn),跟蹤該屬性調(diào)用者及其訪問(wèn)時(shí)機(jī).
說(shuō)了這么多,其實(shí)有一種合理的折中方法:在寫(xiě)入實(shí)例變量時(shí),通過(guò)其"設(shè)置方法"來(lái)做,而在讀取實(shí)例變量時(shí),則直接訪問(wèn)它.此辦法既能提高讀取操作速度,又能控制對(duì)屬性的寫(xiě)入操作.之所以要通過(guò)"設(shè)置方法"來(lái)寫(xiě)入實(shí)例變量,其首要原因在于:這樣能確保相關(guān)屬性的"內(nèi)存管理語(yǔ)義"得以貫徹.但是,選用這樣的做法,還是要注意幾個(gè)問(wèn)題:
- 在初始化方法中該如何設(shè)置屬性值.這種情況綜述應(yīng)該直接訪問(wèn)實(shí)例變量,因?yàn)樽宇惪赡軙?huì)"覆寫(xiě)"設(shè)置方法,假設(shè)CWGPerson有一個(gè)子類叫做CWGSmithPerson,這個(gè)子類專門(mén)表示那些姓"Smith"的人,該子類可能會(huì)覆蓋lastName屬性所對(duì)應(yīng)的設(shè)置方法:
-(void)setLastName:(NSString *)lastName {
[super setLastName:lastName];
if(![lastName isEqualToString:@"Smith"]) {
[NSException raise:NSInvalidArgumentException format:@"Last name must be Smith"];
}
在基類CWGPerson的默認(rèn)初始化方法中,可能會(huì)將姓氏設(shè)為空字符串,此時(shí)若是通過(guò)"設(shè)置方法"來(lái)做,那么調(diào)用的將會(huì)是子類的設(shè)置方法,從而拋出異常,但是密歇情況下卻必需在初始化方法中調(diào)用設(shè)置方法:如果待初始化的實(shí)例變量聲明在超類中,而我們又無(wú)法再子類中直接訪問(wèn)此實(shí)例變量的話,那么就要調(diào)用"設(shè)置方法"了.
- 另外一種就是"惰性初始化".在這樣的情況下必需通過(guò)"獲取方法"來(lái)訪問(wèn)屬性.否則實(shí)例變量就永遠(yuǎn)無(wú)法初始化.
總結(jié):
- 在對(duì)象內(nèi)讀取數(shù)據(jù)時(shí), 應(yīng)該直接通過(guò)實(shí)例變量來(lái)讀,而寫(xiě)入數(shù)據(jù)時(shí),則應(yīng)該通過(guò)屬性來(lái)寫(xiě).
- 在初始化方法及dealloc方法中,總是應(yīng)該直接通過(guò)實(shí)例變量來(lái)讀寫(xiě)數(shù)據(jù)(除一些特殊子類中).
- 有時(shí)會(huì)使用惰性初始化技術(shù)配置數(shù)據(jù),這樣的情況下,需要通過(guò)屬性來(lái)讀取數(shù)據(jù).