屬性應該盡可能描述性地命名,避免縮寫,并且是小寫字母開頭的駝峰命名。我們的工具可以很方便地幫我們自動補全所有東西(嗯。。幾乎所有的,Xcode 的Derived Data 會索引這些命名)。所以沒理由少打幾個字符了,并且最好盡可能在你源碼里表達更多東西。
**例子 :**
~~~
NSString *text;
~~~
**不要這樣 :**
~~~
NSString* text;
NSString * text;
~~~
(注意:這個習慣和常量不同,這是主要從常用和可讀性考慮。 C++ 的開發者偏好從變量名中分離類型,作為類型它應該是`NSString*` (對于從堆中分配的對象,同事對于C++是不能從棧上分配的)格式。)
使用屬性的自動同步 (synthesize) 而不是手動的 `@synthesize` 語句,除非你的屬性是 protocol 的一部分而不是一個完整的類。如果 Xcode 可以自動同步這些變量,就讓它來做吧。否則只會讓你拋開 Xcode 的優點,維護更冗長的代碼。
你應該總是使用 setter 和 getter 方法訪問屬性,除了 `init` 和 `dealloc` 方法。通常,使用屬性讓你增加了在當前作用域之外的代碼塊的可能所以可能帶來更多副作用
你總應該用 getter 和 setter 因為:
- 使用 setter 會遵守定義的內存管理語義(`strong`, `weak`, `copy` etc...) 這回定義更多相關的在ARC是錢,因為它始終是相關的。舉個例子,`copy` 每個時候你用 setter 并且傳送數據的時候,它會復制數據而不用額外的操作
- KVO 通知(`willChangeValueForKey`, `didChangeValueForKey`) 會被自動執行
- 更容易debug:你可以設置一個斷點在屬性聲明上并且斷點會在每次 getter / setter 方法調用的時候執行,或者你可以在自己的自定義 setter/getter 設置斷點。
- 允許在一個單獨的地方為設置值添加額外的邏輯。
你應該傾向于用 getter:
- 它是對未來的變化有擴展能力的(比如,屬性是自動生成的)
- 它允許子類化
- 更簡單的debug(比如,允許拿出一個斷點在 getter 方法里面,并且看誰訪問了特別的 getter
- 它讓意圖更加清晰和明確:通過訪問 ivar `_anIvar` 你可以明確的訪問 `self->_anIvar`.這可能導致問題。在 block 里面訪問 ivar (你捕捉并且 retain 了 sefl 即使你沒有明確的看到 self 關鍵詞)
- 它自動產生KVO 通知
- 在消息發送的時候增加的開銷是微不足道的。更多關于新年問題的介紹你可以看 [Should I Use a Property or an Instance Variable?](http://blog.bignerdranch.com/4005-should-i-use-a-property-or-an-instance-variable/)
### Init 和 Dealloc
有一個例外:你永遠不能在 init (以及其他初始化函數)里面用 getter 和 setter 方法,并且你直接訪問實例變量。事實上一個子類可以重載sette或者getter并且嘗試調用其他方法,訪問屬性的或者 ivar 的話,他們可能沒有完全初始化。記住一個對象是僅僅在 init 返回的時候,才會被認為是初始化完成到一個狀態了。
同樣在 dealloc 方法中(在 dealloc 方法中,一個對象可以在一個 不確定的狀態中)這是同樣需要被注意的。
- [Advanced Memory Management Programming Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW6) under the self-explanatory section "Don't Use Accessor Methods in Initializer Methods and dealloc";
- [Migrating to Modern Objective-C](http://adcdownload.apple.com//wwdc_2012/wwdc_2012_session_pdfs/session_413__migrating_to_modern_objectivec.pdf) at WWDC 2012 at slide 27;
- in a [pull request](https://github.com/NYTimes/objective-c-style-guide/issues/6) form Dave DeLong's.
此外,在 init 中使用 setter 不會很好執行 `UIAppearence` 代理(參見 [UIAppearance for Custom Views](http://petersteinberger.com/blog/2013/uiappearance-for-custom-views/) 看更多相關信息).)
### 點符號
當使用 setter getter 方法的時候盡量使用點符號。應該總是用點符號來訪問以及設置屬性
**例子:**
~~~
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
~~~
**不要這樣:**
~~~
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
~~~
使用點符號會讓表達更加清晰并且幫助區分屬性訪問和方法調用
## 屬性定義
推薦按照下面的格式來定義屬性
~~~
@property (nonatomic, readwrite, copy) NSString *name;
~~~
屬性的參數應該按照下面的順序排列: 原子性,讀寫 和 內存管理。 這樣做你的屬性更容易修改正確,并且更好閱讀。
你必須使用 `nonatomic`,除非特別需要的情況。在iOS中,`atomic`帶來的鎖特別影響性能。
屬性可以存儲一個代碼塊。為了讓它存活到定義的塊的結束,必須使用 `copy` (block 最早在棧里面創建,使用 `copy`讓 block 拷貝到堆里面去)
為了完成一個共有的 getter 和一個私有的 setter,你應該聲明公開的屬性為 `readonly` 并且在類擴展總重新定義通用的屬性為 `readwrite` 的。
~~~
@interface MyClass : NSObject
@property (nonatomic, readonly) NSObject *object
@end
@implementation MyClass ()
@property (nonatomic, readwrite, strong) NSObject *object
@end
~~~
如果 `BOOL` 屬性的名字是描述性的,這個屬性可以省略 "is" ,但是特定要在 get 訪問器中指定名字,如:
~~~
@property (assign, getter=isEditable) BOOL editable;
~~~
文字和例子是引用 [Cocoa Naming Guidelines](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-BAJGIIJE).
為了避免 `@synthesize` 的使用,在實現文件中,Xcode已經自動幫你添加了。
#### 私有屬性
私有屬性應該在類實現文件的類拓展(class extensions,沒有名字的 categories 中)中。有名字的 categories(如果 `ZOCPrivate`)不應該使用,除非拓展另外的類。
**例子:**
~~~
@interface ZOCViewController ()
@property (nonatomic, strong) UIView *bannerView;
@end
~~~
## 可變對象
【疑問】
任何可以用來用一個可變的對象設置的((比如 `NSString`,`NSArray`,`NSURLRequest`))屬性的的內存管理類型必須是 `copy` 的。
這個是用來確保包裝,并且在對象不知道的情況下避免改變值。
你應該同時避免暴露在公開的接口中可變的對象,因為這允許你的類的使用者改變你自己的內部表示并且破壞了封裝。你可以提供可以只讀的屬性來返回你對象的不可變的副本。
~~~
/* .h */
@property (nonatomic, readonly) NSArray *elements
/* .m */
- (NSArray *)elements {
return [self.mutableElements copy];
}
~~~
## 懶加載
當實例化一個對象可能耗費很多資源的,或者需要只配置一次并且有一些配置方法需要調用,而且你還不想弄亂這些方法。
在這個情況下,我們可以選擇使用重載屬性的 getter 方法來做 lazy 實例化。通常這種操作的模板像這樣:
~~~
- (NSDateFormatter *)dateFormatter {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSS"];
}
return _dateFormatter;
}
~~~
即使在一些情況下這是有益的,但是我們仍然建議你在決定這樣做之前經過深思熟慮,事實上這樣是可以避免的。下面是使用 延遲實例化的爭議。
- getter 方法不應該有副作用。在使用 getter 方法的時候你不要想著它可能會創建一個對象或者導致副作用,事實上,如果調用 getter 方法的時候沒有涉及返回的對象,編譯器就會放出警告:getter 不應該產生副作用
- 你在第一次訪問的時候改變了初始化的消耗,產生了副作用,這回讓優化性能變得困難(以及測試)
- 這個初始化可能是不確定的:比如你期望屬性第一次被一個方法訪問,但是你改變了類的實現,訪問器在你預期之前就得到了調用,這樣可以導致問題,特別是初始化邏輯可能依賴于類的其他不同狀態的時候。總的來說最好明確依賴關系。
- 這個行為不是 KVO 友好的。如果 getter 改變了引用,他應該通過一個 KVO 通知來通知改變。當訪問 getter 的時候收到一個改變的通知很奇怪。