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