類名應(yīng)加上 三 個(gè)大寫字母作為前綴(兩個(gè)字母的為 Apple 的類保留)。雖然這個(gè)規(guī)范看起來難看,但是這樣做是為了減少 objective-c 沒有命名空間所帶來的問題。
一些開發(fā)者在定義 Model 對(duì)象時(shí)并不遵循這個(gè)規(guī)范(對(duì)于 Core Data 對(duì)象,我們更應(yīng)該遵循這個(gè)規(guī)范)。我們建議在定義 Core Data 對(duì)象時(shí)嚴(yán)格遵循這個(gè)約定,因?yàn)槟阕詈罂赡馨涯愕?Managed Object Model 和其他(第三方庫)的 Managed Object Model 合并。
你可能注意到了,這本書里的類的前綴(其實(shí)不僅僅是類)是ZOC
。
另一個(gè)類的命名規(guī)范:當(dāng)你創(chuàng)建一個(gè)子類的時(shí)候,你應(yīng)該把說明性的部分放在前綴和父類名的在中間。舉個(gè)例子:如果你有一個(gè) ZOCNetworkClient
類,子類的名字會(huì)是ZOCTwitterNetworkClient
(注意 "Twitter" 在 "ZOC" 和 "NetworkClient" 之間); 按照這個(gè)約定, 一個(gè)UIViewController
的子類會(huì)是 ZOCTimelineViewController
.
推薦的代碼組織方式:將 dealloc
方法放在實(shí)現(xiàn)文件的最前面(直接在 @synthesize
以及 @dynamic
之后),init
應(yīng)該放在 dealloc
之后。如果有多個(gè)初始化方法, designated initializer 應(yīng)該放在第一個(gè),secondary initializer 在之后緊隨,這樣邏輯性更好。
如今有了 ARC,dealloc 方法幾乎不需要實(shí)現(xiàn),不過把 init 和 dealloc 放在一起可以從視覺上強(qiáng)調(diào)它們是一對(duì)的。通常,在 init 方法中做的事情需要在 dealloc 方法中撤銷。
init
方法應(yīng)該是這樣的結(jié)構(gòu):
- (instancetype)init
{
self = [super init]; // call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
為什么設(shè)置 self
為 [super init]
的返回值,以及中間發(fā)生了什么呢?這是一個(gè)十分有趣的話題。
讓我們后退一步:我們?cè)?jīng)寫了類似 [[NSObject alloc] init]
的表達(dá)式, alloc
和 init
區(qū)別慢慢褪去 。一個(gè) Objective-C 的特性叫 兩步創(chuàng)建 。 這意味著申請(qǐng)分配內(nèi)存和初始化是兩個(gè)分離的操作。
alloc
表示對(duì)象分配內(nèi)存,這個(gè)過程涉及分配足夠的可用內(nèi)存來保存對(duì)象,寫入isa
指針,初始化 retain 的計(jì)數(shù),并且初始化所有實(shí)例變量。init
是表示初始化對(duì)象,這意味著把對(duì)象放到了一個(gè)可用的狀態(tài)。這通常是指把對(duì)象的實(shí)例變量賦給了可用的值。alloc
方法會(huì)返回一個(gè)合法的沒有初始化的實(shí)例對(duì)象。每一個(gè)發(fā)送到實(shí)例的信息會(huì)被翻譯為名字是 self
的 alloc
返回的指針的參數(shù)返回的 objc_msgSend()
的調(diào)用。這樣之后 self
已經(jīng)可以執(zhí)行所有方法了。
為了完成兩步創(chuàng)建,第一個(gè)發(fā)送給新創(chuàng)建的實(shí)例的方法應(yīng)該是約定俗成的 init
方法。注意 NSObject
的 init
實(shí)現(xiàn)中,僅僅是返回了 self
。
關(guān)于 init
有一個(gè)另外的重要的約定:這個(gè)方法可以(并且應(yīng)該)在不能成功完成初始化的時(shí)候返回 nil
;初始化可能因?yàn)楦鞣N原因失敗,比如一個(gè)輸入的格式錯(cuò)誤,或者未能成功初始化一個(gè)需要的對(duì)象。
這樣我們就理解了為什么需要總是調(diào)用 self = [super init]
。如果你的超類沒有成功初始化它自己,你必須假設(shè)你在一個(gè)矛盾的狀態(tài),并且在你的實(shí)現(xiàn)中不要處理你自己的初始化邏輯,同時(shí)返回 nil
。如果你不是這樣做,你看你會(huì)得到一個(gè)不能用的對(duì)象,并且它的行為是不可預(yù)測(cè)的,最終可能會(huì)導(dǎo)致你的 app 發(fā)生 crash。
重新給 self
賦值同樣可以被 init
利用為在被調(diào)用的時(shí)候返回不同的實(shí)例。一個(gè)例子是 類簇 或者其他的返回相同的(不可變的)實(shí)例對(duì)象的 Cocoa 類。
Objective-C 有 designated 和 secondary 初始化方法的觀念。 designated 初始化方法是提供所有的參數(shù),secondary 初始化方法是一個(gè)或多個(gè),并且提供一個(gè)或者更多的默認(rèn)參數(shù)來調(diào)用 designated 初始化方法的初始化方法。
@implementation ZOCEvent
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date
location:(CLLocation *)location
{
self = [super init];
if (self) {
_title = title;
_date = date;
_location = location;
}
return self;
}
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date
{
return [self initWithTitle:title date:date location:nil];
}
- (instancetype)initWithTitle:(NSString *)title
{
return [self initWithTitle:title date:[NSDate date] location:nil];
}
@end
initWithTitle:date:location:
就是 designated 初始化方法,另外的兩個(gè)是 secondary 初始化方法。因?yàn)樗鼈儍H僅是調(diào)用類實(shí)現(xiàn)的 designated 初始化方法
一個(gè)類應(yīng)該又且只有一個(gè) designated 初始化方法,其他的初始化方法應(yīng)該調(diào)用這個(gè) designated 的初始化方法(雖然這個(gè)情況有一個(gè)例外)
這個(gè)分歧沒有要求那個(gè)初始化函數(shù)需要被調(diào)用。
在類繼承中調(diào)用任何 designated 初始化方法都是合法的,而且應(yīng)該保證 所有的 designated initializer 在類繼承中是是從祖先(通常是 NSObject
)到你的類向下調(diào)用的。
實(shí)際上這意味著第一個(gè)執(zhí)行的初始化代碼是最遠(yuǎn)的祖先,然后從頂向下的類繼承,所有類都有機(jī)會(huì)執(zhí)行他們特定的初始化代碼。這樣,你在你做你的特定的初始化工作前,所有你從超類繼承的東西是不可用的狀態(tài)。即使它的狀態(tài)不明確,所有 Apple 的框架的 Framework 是保證遵守這個(gè)約定的,而且你的類也應(yīng)該這樣做。
當(dāng)定義一個(gè)新類的時(shí)候有三個(gè)不同的方式:
第一個(gè)方案是最簡(jiǎn)單的:你不需要增加類的任何初始化邏輯,只需要依照父類的designated initializer。
當(dāng)你希望提供額外的初始化邏輯的時(shí)候你可以重載 designated initializer。你只需要重載你的直接的超類的 designated initializer 并且確認(rèn)你的實(shí)現(xiàn)調(diào)用了超類的方法。
你一個(gè)典型的例子是你創(chuàng)造UIViewController
子類的時(shí)候重載
initWithNibName:bundle:
方法。
@implementation ZOCViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call to the superclass designated initializer
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
@end
在 UIViewController
子類的例子里面如果重載 init
會(huì)是一個(gè)錯(cuò)誤,這個(gè)情況下調(diào)用者會(huì)嘗試調(diào)用 initWithNib:bundle
初始化你的類,你的類實(shí)現(xiàn)不會(huì)被調(diào)用。著同樣違背了它應(yīng)該是合法調(diào)用任何 designated initializer 的規(guī)則。
在你希望提供你自己的初始化函數(shù)的時(shí)候,你應(yīng)該遵守這三個(gè)步驟來保證正確的性:
很多開發(fā)者忽略了后兩步,這不僅僅是一個(gè)粗心的問題,而且這樣違反了框架的規(guī)則,而且可能導(dǎo)致不確定的行為和bug。 讓我們看看正確的實(shí)現(xiàn)的例子:
@implementation ZOCNewsViewController
- (id)initWithNews:(ZOCNews *)news
{
// call to the immediate superclass's designated initializer
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}
// Override the immediate superclass's designated initializer (重載直接父類的 designated initializer)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call the new designated initializer
return [self initWithNews:nil];
}
@end
你沒重載 initWithNibName:bundle:
而且調(diào)用者決定用這個(gè)方法初始化你的類(這是完全合法的)。 initWithNews:
永遠(yuǎn)不會(huì)被調(diào)用,所以導(dǎo)致了不正確的初始化流程,你的類特定的初始化邏輯沒有被執(zhí)行。
即使可以推斷那個(gè)方法是 designate initializer它,但是最好清晰地明確(未來的你或者其他開發(fā)者在改代碼的時(shí)候會(huì)感謝你的)。你應(yīng)該考慮來用這兩個(gè)策略(不是互斥的):第一個(gè)是你在文檔中明確哪一個(gè)初始化方法是 designated 的,但是最好你可以用編譯器的指令 __attribute__((objc_designated_initializer))
來標(biāo)記你的意圖。
用這個(gè)編譯指令的時(shí)候,編譯器回來幫你。如果你的新的 designate initializer 沒有調(diào)用你超類的 designated initializer,上編譯器會(huì)發(fā)出警告。
然而,當(dāng)沒有調(diào)用類的 designated initializer 的時(shí)候(并且依次提供必要的參數(shù)),并且調(diào)用其他父類中的 designated initialize 的時(shí)候,會(huì)變成一個(gè)不可用的狀態(tài)。參考之前的例子,當(dāng)實(shí)例化一個(gè) ZOCNewsViewController
展示一個(gè)新聞而那條新聞沒有展示的話,就會(huì)毫無意義。這個(gè)情況下你應(yīng)該只需要讓其他的 designated initializer 失效,來強(qiáng)制調(diào)用一個(gè)非常特別的 designated initializer。通過使用另外一個(gè)編譯器指令 __attribute__((unavailable("Invoke the designated initializer")))
來修飾一個(gè)方法,通過這個(gè)屬性,會(huì)讓你在試圖調(diào)用這個(gè)方法的時(shí)候產(chǎn)生一個(gè)編譯錯(cuò)誤。
這是之前的例子相關(guān)的實(shí)現(xiàn)的頭文件(這里使用宏來讓代碼沒有那么啰嗦)
@interface ZOCNewsViewController : UIViewController
- (instancetype)initWithNews:(ZOCNews *)news ZOC_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ZOC_UNAVAILABLE_INSTEAD(initWithNews:);
- (instancetype)init ZOC_UNAVAILABLE_INSTEAD(initWithNews:);
@end
上述的一個(gè)推論是:你應(yīng)該永遠(yuǎn)不從 designated initializer 里面調(diào)用一個(gè) secondary initializer (如果secondary initializer 遵守約定,它會(huì)調(diào)用 designated initializer)。如果這樣,調(diào)用很可能會(huì)調(diào)用一個(gè)子類重寫的 init 方法并且陷入無限遞歸之中。
然而一個(gè)意外是一個(gè)對(duì)象是否遵守 NSCoding
協(xié)議,并且它通過方法 initWithCoder:
初始化。
我們應(yīng)該區(qū)別超類是否符合 NSCoding
的情況。
如果符合,如果你只是調(diào)用 [super initWithCoder:]
你會(huì)可能有一個(gè)共享的初始化代碼在 designated initializer 里面,一個(gè)好的方法是吧這些代碼放在私有方法里面(比如 p_commonInit
)。
當(dāng)你的超類不符合NSCoding
協(xié)議的時(shí)候,推薦把 initWithCoder:
作為 secondary initializer 來對(duì)待,并且調(diào)用 self
的 designated initializer。 注意這是違反 Apple 的 Archives and Serializations Programming Guide 上面寫的:
the object should first invoke its superclass's designated initializer to initialize inherited state (對(duì)象總是應(yīng)該首先調(diào)用超類的 designated initializer 來初始化繼承的狀態(tài))
如果你的類不是 NSObject
的直接子類,這樣做的話,會(huì)導(dǎo)致不可預(yù)測(cè)的行為。
正如之前的描述么,secondary initializer 是一種方便提供默認(rèn)值、行為到 designated initializer 的 方法。也就是說,你不應(yīng)該強(qiáng)制很多初始化操作在這樣的方法里面,并且你應(yīng)該一直假設(shè)這個(gè)方法不會(huì)得到調(diào)用。我們保證的是唯一被調(diào)用的方法是 designated initializer。
這意味著你的 designated initializer 總是應(yīng)該調(diào)用其他的 secondary initializer 或者你 self
的 designated initializer。有時(shí)候,因?yàn)殄e(cuò)誤,可能打成了 super
,這樣會(huì)導(dǎo)致不符合上面提及的初始化順序(在這個(gè)特別的例子里面,是跳過當(dāng)前類的初始化)
我們經(jīng)常忽略 Cocoa 充滿了約定,并且這些約定可以幫助編譯器變得更加聰明。無論編譯器是否遭遇 alloc
或者 init
方法,他會(huì)知道,即使返回類型都是 id
,這些方法總是返回接受到的類類型的實(shí)例。因此,它允許編譯器進(jìn)行類型檢查。(比如,檢查方法返回的類型是否合法)。Clang的這個(gè)好處來自于 related result type, 意味著:
messages sent to one of alloc and init methods will have the same static type as the instance of the receiver class (發(fā)送到 alloc 或者 init 方法的消息會(huì)有同樣的靜態(tài)類型檢查是否為接受類的實(shí)例。)
更多的關(guān)于這個(gè)自動(dòng)定義相關(guān)返回類型的約定請(qǐng)查看 Clang Language Extensions guide 的appropriate section
一個(gè)相關(guān)的返回類型可以明確地規(guī)定用 instancetype
關(guān)鍵字作為返回類型,并且它可以在一些工廠方法或者構(gòu)造器方法的場(chǎng)景下很有用。它可以提示編譯器正確地檢查類型,并且更加重要的是,這同時(shí)適用于它的子類。
@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name;
@end
雖然如此,根據(jù) clang 的定義,id
可以被編譯器提升到 instancetype
。在 alloc
或者 init
中,我們強(qiáng)烈建議對(duì)所有返回類的實(shí)例的類方法和實(shí)例方法使用 instancetype
類型。
在你的 API 中要構(gòu)成習(xí)慣以及保持始終如一的,此外,通過對(duì)你代碼的小調(diào)整你可以提高可讀性:在簡(jiǎn)單的瀏覽的時(shí)候你可以區(qū)分哪些方法是返回你類的實(shí)例的。你以后會(huì)感謝這些注意過的小細(xì)節(jié)的。
類簇在Apple的文檔中這樣描述:
an architecture that groups a number of private, concrete subclasses under a public, abstract superclass. (一個(gè)在共有的抽象超類下設(shè)置一組私有子類的架構(gòu))
如果這個(gè)描述聽起來很熟悉,說明你的直覺是對(duì)的。 Class cluster 是 Apple 對(duì)抽象工廠設(shè)計(jì)模式的稱呼。
class cluster 的想法很簡(jiǎn)單,你經(jīng)常有一個(gè)抽象類在初始化期間處理信息,經(jīng)常作為一個(gè)構(gòu)造器里面的參數(shù)或者環(huán)境中讀取,來完成特定的邏輯并且實(shí)例化子類。這個(gè)"public facing" 應(yīng)該知曉它的子類而且返回適合的私有子類。
這個(gè)模式非常有用,因?yàn)樗鼫p少了構(gòu)造器調(diào)用中的復(fù)雜性,只需要知道接口如何與對(duì)象通信,而不需要知道怎么實(shí)現(xiàn)。
Class clusters 在 Apple 的Framework 中廣泛使用:一些明顯的例子比如 NSNumber
可以返回不同哦給你的子類,取決于 數(shù)字類型如何提供 (Integer, Float, etc...) 或者 NSArray
返回不同的最優(yōu)存儲(chǔ)策略的子類。
這個(gè)模式的精妙的地方在于,調(diào)用者可以完全不管子類,事實(shí)上,這可以用在設(shè)計(jì)一個(gè)庫,可以用來交換實(shí)際的返回的類,而不用去管相關(guān)的細(xì)節(jié),因?yàn)樗鼈兌甲駨某橄蟪惖姆椒ā?/p>
我們的經(jīng)驗(yàn)是使用類簇可以幫助移除很多條件語句。
一個(gè)經(jīng)典的例子是如果你有為 iPad 和 iPhone 寫的一樣的 UIViewController 子類,但是在不同的設(shè)備上有不同的行為。
比較基礎(chǔ)的實(shí)現(xiàn)是用條件語句檢查設(shè)備,然后執(zhí)行不同的邏輯。雖然剛開始可能不錯(cuò),但是隨著代碼的增長(zhǎng),運(yùn)行邏輯也會(huì)趨于復(fù)雜。 一個(gè)更好的實(shí)現(xiàn)的設(shè)計(jì)是創(chuàng)建一個(gè)抽象而且寬泛的 view controller 來包含所有的共享邏輯,并且對(duì)于不同設(shè)備有兩個(gè)特別的子例。
通用的 view controller 會(huì)檢查當(dāng)前設(shè)備并且返回適當(dāng)?shù)淖宇悺?/p>
@implementation ZOCKintsugiPhotoViewController
- (id)initWithPhotos:(NSArray *)photos
{
if ([self isMemberOfClass:ZOCKintsugiPhotoViewController.class]) {
self = nil;
if ([UIDevice isPad]) {
self = [[ZOCKintsugiPhotoViewController_iPad alloc] initWithPhotos:photos];
}
else {
self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];
}
return self;
}
return [super initWithNibName:nil bundle:nil];
}
@end
之前的代碼的例子展示了如何創(chuàng)建一個(gè)類簇。首先,[self isMemberOfClass:ZOCKintsugiPhotoViewController.class]
來避免在子類中重載初始化方法,來避免無限的遞歸。當(dāng) [[ZOCKintsugiPhotoViewController alloc] initWithPhotos:photos]
得到調(diào)用的時(shí)候之前的檢查會(huì)變成 true 的,self = nil
是用來移除所有到 ZOCKintsugiPhotoViewController
實(shí)例的引用的,它會(huì)被釋放,按照這個(gè)邏輯來檢查哪個(gè)類應(yīng)該被初始化。
讓我們假設(shè)在 iPhone 上運(yùn)行了這個(gè)代碼, ZOCKintsugiPhotoViewController_iPhone
沒有重載initWithPhotos:
,在這個(gè)情況下,當(dāng)執(zhí)行 self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];
的時(shí)候,ZOCKintsugiPhotoViewController
會(huì)被調(diào)用,并且當(dāng)?shù)谝淮螜z查的時(shí)候,這樣不會(huì)讓 ZOCKintsugiPhotoViewController
檢查會(huì)變成 false 調(diào)用return [super initWithNibName:nil bundle:nil];
,這會(huì)讓 繼續(xù)初始化執(zhí)行正確的初始化之前的會(huì)話。
如果可能,請(qǐng)盡量避免使用單例而是依賴注入。
然而,如果一定要用,請(qǐng)使用一個(gè)線程安全的模式來創(chuàng)建共享的實(shí)例。 對(duì)于GCD,用 dispatch_once()
函數(shù)就可以咯。
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
使用dispatch_once(),來控制代碼同步,取代了原來老的約定俗成的用法。
+ (instancetype)sharedInstance
{
static id sharedInstance;
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[MyClass alloc] init];
}
}
return sharedInstance;
}
dispatch_once()
的優(yōu)點(diǎn)是,它更快,而且語法上更干凈,因?yàn)閐ispatch_once()的意思就是 ”把一些東西執(zhí)行一次“,就像我們做的一樣。 這樣同時(shí)可以避免possible and sometimes prolific crashes.
經(jīng)典的可以接受的單例對(duì)象的例子是一個(gè)設(shè)備的 GPS 以及 動(dòng)作傳感器。即使單例對(duì)象可以被子類化,這個(gè)情況可以十分有用。這個(gè)接口應(yīng)該證明給出的類是趨向于使用單例的。然而,經(jīng)常使用一個(gè)單獨(dú)的公開的 sharedInstance
類方法就夠了,并且不可寫的屬性也應(yīng)該被暴露。
把單例作為一個(gè)對(duì)象的容器來在代碼或者應(yīng)用層面上共享是糟糕和丑陋的,這是一個(gè)不好的設(shè)計(jì)。
屬性應(yīng)該盡可能描述性地命名,避免縮寫,并且是小寫字母開頭的駝峰命名。我們的工具可以很方便地幫我們自動(dòng)補(bǔ)全所有東西(嗯。。幾乎所有的,Xcode 的Derived Data 會(huì)索引這些命名)。所以沒理由少打幾個(gè)字符了,并且最好盡可能在你源碼里表達(dá)更多東西。
例子 :
NSString *text;
不要這樣 :
NSString* text;
NSString * text;
(注意:這個(gè)習(xí)慣和常量不同,這是主要從常用和可讀性考慮。 C++ 的開發(fā)者偏好從變量名中分離類型,作為類型它應(yīng)該是
NSString*
(對(duì)于從堆中分配的對(duì)象,同事對(duì)于C++是不能從棧上分配的)格式。)
使用屬性的自動(dòng)同步 (synthesize) 而不是手動(dòng)的 @synthesize
語句,除非你的屬性是 protocol 的一部分而不是一個(gè)完整的類。如果 Xcode 可以自動(dòng)同步這些變量,就讓它來做吧。否則只會(huì)讓你拋開 Xcode 的優(yōu)點(diǎn),維護(hù)更冗長(zhǎng)的代碼。
你應(yīng)該總是使用 setter 和 getter 方法訪問屬性,除了 init
和 dealloc
方法。通常,使用屬性讓你增加了在當(dāng)前作用域之外的代碼塊的可能所以可能帶來更多副作用
你總應(yīng)該用 getter 和 setter 因?yàn)椋?/p>
strong
, weak
, copy
etc...) 這回定義更多相關(guān)的在ARC是錢,因?yàn)樗冀K是相關(guān)的。舉個(gè)例子,copy
每個(gè)時(shí)候你用 setter 并且傳送數(shù)據(jù)的時(shí)候,它會(huì)復(fù)制數(shù)據(jù)而不用額外的操作willChangeValueForKey
, didChangeValueForKey
) 會(huì)被自動(dòng)執(zhí)行你應(yīng)該傾向于用 getter:
_anIvar
你可以明確的訪問 self->_anIvar
.這可能導(dǎo)致問題。在 block 里面訪問 ivar (你捕捉并且 retain 了 sefl 即使你沒有明確的看到 self 關(guān)鍵詞)有一個(gè)例外:你永遠(yuǎn)不能在 init (以及其他初始化函數(shù))里面用 getter 和 setter 方法,并且你直接訪問實(shí)例變量。事實(shí)上一個(gè)子類可以重載sette或者getter并且嘗試調(diào)用其他方法,訪問屬性的或者 ivar 的話,他們可能沒有完全初始化。記住一個(gè)對(duì)象是僅僅在 init 返回的時(shí)候,才會(huì)被認(rèn)為是初始化完成到一個(gè)狀態(tài)了。
同樣在 dealloc 方法中(在 dealloc 方法中,一個(gè)對(duì)象可以在一個(gè) 不確定的狀態(tài)中)這是同樣需要被注意的。
此外,在 init 中使用 setter 不會(huì)很好執(zhí)行 UIAppearence
代理(參見 UIAppearance for Custom Views 看更多相關(guān)信息).)
當(dāng)使用 setter getter 方法的時(shí)候盡量使用點(diǎn)符號(hào)。應(yīng)該總是用點(diǎn)符號(hào)來訪問以及設(shè)置屬性
例子:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不要這樣:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
使用點(diǎn)符號(hào)會(huì)讓表達(dá)更加清晰并且?guī)椭鷧^(qū)分屬性訪問和方法調(diào)用
推薦按照下面的格式來定義屬性
@property (nonatomic, readwrite, copy) NSString *name;
屬性的參數(shù)應(yīng)該按照下面的順序排列: 原子性,讀寫 和 內(nèi)存管理。 這樣做你的屬性更容易修改正確,并且更好閱讀。
你必須使用 nonatomic
,除非特別需要的情況。在iOS中,atomic
帶來的鎖特別影響性能。
屬性可以存儲(chǔ)一個(gè)代碼塊。為了讓它存活到定義的塊的結(jié)束,必須使用 copy
(block 最早在棧里面創(chuàng)建,使用 copy
讓 block 拷貝到堆里面去)
為了完成一個(gè)共有的 getter 和一個(gè)私有的 setter,你應(yīng)該聲明公開的屬性為 readonly
并且在類擴(kuò)展總重新定義通用的屬性為 readwrite
的。
@interface MyClass : NSObject
@property (nonatomic, readonly) NSObject *object
@end
@implementation MyClass ()
@property (nonatomic, readwrite, strong) NSObject *object
@end
如果 BOOL
屬性的名字是描述性的,這個(gè)屬性可以省略 "is" ,但是特定要在 get 訪問器中指定名字,如:
@property (assign, getter=isEditable) BOOL editable;
文字和例子是引用 Cocoa Naming Guidelines.
為了避免 @synthesize
的使用,在實(shí)現(xiàn)文件中,Xcode已經(jīng)自動(dòng)幫你添加了。
私有屬性應(yīng)該在類實(shí)現(xiàn)文件的類拓展(class extensions,沒有名字的 categories 中)中。有名字的 categories(如果 ZOCPrivate
)不應(yīng)該使用,除非拓展另外的類。
例子:
@interface ZOCViewController ()
@property (nonatomic, strong) UIView *bannerView;
@end
【疑問】
任何可以用來用一個(gè)可變的對(duì)象設(shè)置的((比如 NSString
,NSArray
,NSURLRequest
))屬性的的內(nèi)存管理類型必須是 copy
的。
這個(gè)是用來確保包裝,并且在對(duì)象不知道的情況下避免改變值。
你應(yīng)該同時(shí)避免暴露在公開的接口中可變的對(duì)象,因?yàn)檫@允許你的類的使用者改變你自己的內(nèi)部表示并且破壞了封裝。你可以提供可以只讀的屬性來返回你對(duì)象的不可變的副本。
/* .h */
@property (nonatomic, readonly) NSArray *elements
/* .m */
- (NSArray *)elements {
return [self.mutableElements copy];
}
當(dāng)實(shí)例化一個(gè)對(duì)象可能耗費(fèi)很多資源的,或者需要只配置一次并且有一些配置方法需要調(diào)用,而且你還不想弄亂這些方法。
在這個(gè)情況下,我們可以選擇使用重載屬性的 getter 方法來做 lazy 實(shí)例化。通常這種操作的模板像這樣:
- (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;
}
即使在一些情況下這是有益的,但是我們?nèi)匀唤ㄗh你在決定這樣做之前經(jīng)過深思熟慮,事實(shí)上這樣是可以避免的。下面是使用 延遲實(shí)例化的爭(zhēng)議。
你的方法可能要求一些參數(shù)來滿足特定的條件(比如不能為nil),在這種情況下啊最好使用 NSParameterAssert()
來斷言條件是否成立或是拋出一個(gè)異常。
永遠(yuǎn)不要在你的私有方法前加上 _
前綴。這個(gè)前綴是 Apple 保留的。不要冒重載蘋果的私有方法的險(xiǎn)。
當(dāng)你要實(shí)現(xiàn)相等性的時(shí)候記住這個(gè)約定:你需要同時(shí)實(shí)現(xiàn)isEqual
and the hash
方法。如果兩個(gè)對(duì)象是被isEqual
認(rèn)為相等的,它們的 hash
方法需要返回一樣的值。但是如果 hash
返回一樣的值,并不能確保他們相等。
這個(gè)約定是因?yàn)楫?dāng)被存儲(chǔ)在集合(如 NSDictionary
和 NSSet
在底層使用 hash 表數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu))的時(shí)候,如何查找這些對(duì)象。
@implementation ZOCPerson
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[ZOCPerson class]]) {
return NO;
}
// check objects properties (name and birthday) for equality
...
return propertiesMatch;
}
- (NSUInteger)hash {
return [self.name hash] ^ [self.birthday hash];
}
@end
一定要注意 hash 方法不能返回一個(gè)常量。這是一個(gè)典型的錯(cuò)誤并且會(huì)導(dǎo)致嚴(yán)重的問題,因?yàn)槭褂昧诉@個(gè)值作為 hash 表的 key,會(huì)導(dǎo)致 hash 表 100%的碰撞
你總是應(yīng)該用 isEqualTo<#class-name-without-prefix#>:
這樣的格式實(shí)現(xiàn)一個(gè)相等性檢查方法。如果你這樣做,會(huì)優(yōu)先調(diào)用這個(gè)方法來避免上面的類型檢查。
一個(gè)完整的 isEqual* 方法應(yīng)該是這樣的:
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[ZOCPerson class]]) {
return NO;
}
return [self isEqualToPerson:(ZOCPerson *)object];
}
- (BOOL)isEqualToPerson:(Person *)person {
if (!person) {
return NO;
}
BOOL namesMatch = (!self.name && !person.name) ||
[self.name isEqualToString:person.name];
BOOL birthdaysMatch = (!self.birthday && !person.birthday) ||
[self.birthday isEqualToDate:person.birthday];
return haveEqualNames && haveEqualBirthdays;
}
一個(gè)對(duì)象實(shí)例的 hash
計(jì)算結(jié)果應(yīng)該是確定的。當(dāng)它被加入到一個(gè)容器對(duì)象(比如 NSArray
, NSSet
, 或者 NSDictionary
)的時(shí)候這是很重要的,否則行為會(huì)無法預(yù)測(cè)(所有的容器對(duì)象使用對(duì)象的 hash 來查找或者實(shí)施特別的行為,如確定唯一性)這也就是說,應(yīng)該用不可變的屬性來計(jì)算 hash 值,或者,最好保證對(duì)象是不可變的。
更多建議: