標籤:
在對象之外訪問執行個體變數時,應該總是通過屬性來做.在那麼在對象內部訪問執行個體變數的時候,又該如何呢?
這是 OCer們一直激烈討論的問題.有人認為,無論什麼情況,都應該通過屬性來訪問執行個體變數;也有人說,”通過屬性訪問”和”直接存取”應該搭配著用. 除了幾種特殊情況之外, 筆者強烈建議大家在讀取執行個體變數的時候採用直接存取的形式,而在設定執行個體變數的時候通過屬性來做.
請看下面的類:
@interface EOCPerson : NSObject@property(nonatomic,copy)NSString *firstName;@property(nonatomic,copy)NSString *lastName;//設定全名的快捷方法-(NSString*)fullName;-(void)setFullName:(NSString*)fullName;@end
fullName和 setFullName這兩個”便捷方法”,可以這樣來實現:
-(NSString*)fullName{ return [NSString stringWithFormat:@"%@ %@",self.firstName,self.lastName];}/** * 下面的方法假設所有的全名有且僅有兩部分,當然這個方法也能被改寫,來支援外來姓名 */-(void)setFullName:(NSString *)fullName{ NSArray* components = [fullName componentsSeparatedByString:@" "]; self.firstName = [components objectAtIndex:0]; self.lastName = [components objectAtIndex:1];}
在fullName的擷取與設定方法中,我們使用”點文法”,通過儲存方法來訪問相關執行個體變數. 假設重寫這兩個方法,不經由存取方法,而是直接存取執行個體變數:
-(NSString*)fullName{ return [NSString stringWithFormat:@"%@ %@",_firstName,_lastName];}-(void)setFullName:(NSString *)fullName{ NSArray *components = [fullName componentsSeparatedByString:@" "]; _firstName = [components objectAtIndex:0]; _lastName = [components objectAtIndex:1];}
這兩種寫法有幾個區別:
由於不經過 OC的 方法派發( method dispatch ),所以直接存取執行個體變數的速度當然比較快. 在這種情況下,編譯器所產生的程式碼會直接存取對象執行個體變數的那塊記憶體.
直接存取執行個體變數時,不會調用其設定方法. 這就繞過了為相關屬性所定義的”記憶體管理定義”.比如,在ARC下直接存取一個聲明為 copy的屬性,那麼並不會copy該屬性,只會保留新值並釋放舊值.
如果直接存取執行個體變數,就不會觸發 KVO通知,這樣做是否會產生問題,還取決於具體的對象行為.
通過屬性來訪問有助於排查與之相關的錯誤,因為可以setter添加斷點,監控該屬性的調用者以及訪問時機.
有一種合理的這種方案,那就是:在寫入執行個體變數時,通過其 setter來做,而在讀取執行個體變數的時候,直接存取之.這樣,就技能提高讀取操作的速度,又能監控對屬性的寫入操作.之所以要通過setter來寫入執行個體變數,其首要原因在於,這樣做能夠確保相關屬性的”記憶體管理定義”得以貫徹.但是,選用這種方法時,需要注意幾個問題.
第一個要注意的地方是,在初始化方法中,應該如何設定屬性值.這種情況下總是應該直接存取執行個體變數,因為子類可能會 覆寫(override)設定方法.
在上例中,假設EOCPerson有一子類叫做 EOCSmithPerson,這個類表示那些姓 Smith 的人.該子類可能會override lastName所對應的設定方法:
-(void)setLastName:(NSString *)lastName{ if (![lastName isEqualToString:@"Smith"]) { [NSException raise:NSInvalidArgumentException format:@"Last name must be Smith "]; } self.lastName = lastName;}
在父類 EOCPerson的預設初始化方法中,可能會將姓氏設為空白字串.此時若是通過 setter方法來做,那麼調用的將是子類的設定方法,從而拋出異常.但是某些情況下有必須在初始化方法中調用該設定方法:如果待初始化的執行個體變數聲明在父類中,而我們又無法在子類中直接存取此執行個體變數的話,就需要調用 setter 了.
另一個要主要的問題是:懶載入.在這種情況下,必須通過 getter訪問屬性,否則執行個體變數就永遠不會初始化.比如,EOCPerson類也許會用一個屬性來表示人腦中的資訊,這個屬性所代指的對象相當複雜.由於此屬性不常用,而且建立成本較高,所以,我們會在 getter中對其進行懶載入.
-(EOCBrain*)brain{ if(!_brain) { _barin = [Brain new]; } return brain;}
在這種情況下,如果沒有使用 getter 方法,而直接存取執行個體變數,則會看到沒有初始化的 brain ,所以說,如果使用了懶載入,就必須通過getter 來訪問brain屬性.
歸納:
在對象內部讀取資料時候,應該通過執行個體變數來讀,而寫入資料是,則應該通過屬性來寫.
在初始化以及 dealloc方法中,總是應該通過執行個體變數來讀寫資料
有時會使用懶載入技術配置某些資料,這種情況下,需要通過屬性來讀取資料.
在對象內部盡量直接存取執行個體變數 --Effictive Objective-C 抄書