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