1.書寫規范
條件語句體應該總是被大括號包圍來避免錯誤,即使可以不用(比如,只有一行內容)。這些錯誤包括多加了第二行,並且誤以為它是 if 語句體裡面的。此外,更危險的可能是,如果把 if 語句體裡的一行注釋掉了,之後的一行代碼會不知不覺成為 if 語句裡的代碼。因此,條件語句應該使用被大括號包圍的方式去寫。
推薦:
if (!error) {
return success;
}
2.尤達表達式
不要使用尤達表達式。尤達表達式是指,拿一個常量去和變量比較而不是拿變量去和常量比較。它就像是在表達 “藍色是不是天空的顏色” 或者 “高個是不是這個男人的屬性” 而不是 “天空是不是藍的” 或者 “這個男人是不是高個子的。
推薦:
if ([myValue isEqual:@42]) { ...
不推薦:
if ([@42 isEqual:myValue]) { ...
3.nil 和 BOOL 檢查
檢查途徑是使用感歎號來判斷。因為 nil 是 解釋到 NO 所以沒必要在條件語句裡面把它和其他值比較。同時,不要直接把它和 YES 比較,因為 YES 的定義是 1 而 BOOL 是 8 位的,實際上是 char 類型。
推薦:
if (someObject) { ...
if (![someObject boolValue]) { ...
if (!someObject) { ...
不推薦:
if (someObject == YES) { ... // Wrong
if (myRawValue == YES) { ... // Never do this.
if ([someObject boolValue] == NO) { ...
4.黃金大道
當編寫條件語句的時候,左邊的代碼間距應該是一個“黃金”或者“快樂”的大道。 這是說,不要嵌套if語句。多個 return 語句是OK的。這樣可以避免 Cyclomatic 復雜性,並且讓代碼更加容易閱讀。因為你的方法的重要的部分沒有嵌套在分支上,你可以很清楚地找到相關的代碼。
推薦:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
不推薦:
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
5.復雜的表達式
當你有一個復雜的 if 子句的時候,你應該把它們提取出來賦給一個 BOOL 變量,這樣可以讓邏輯更清楚,而且讓每個子句的意義體現出來
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
6.三元運算符
三元運算符 ? 應該只用在它能讓代碼更加清楚的地方。 一個條件語句的所有的變量應該是已經被求值了的。計算多個條件子句通常會讓語句更加難以理解,就像if語句的情況一樣,或者把它們重構到實例變量裡面。
推薦:
result = a > b ? x : y;
不推薦:
result = a > b ? x = c > d ? c : d : y;
當三元運算符的第二個參數(if 分支)返回和條件語句中已經檢查的對象一樣的對象的時候,下面的表達方式更靈巧:
推薦:
result = object ? : [self createObject];
不推薦:
result = object ? object : [self createObject];
7.錯誤處理
當方法返回一個錯誤參數的引用的時候,檢查返回值,而不是錯誤的變量。
推薦:
NSError *error = nil;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
此外,一些蘋果的 API 在成功的情況下會對 error 參數(如果它非 NULL)寫入垃圾值(garbage values),所以如果檢查 error 的值可能導致錯誤 (甚至崩潰)。
二、Case語句
1.書寫規范
除非編譯器強制要求,括號在 case 語句裡面是不必要的。但是當一個 case 包含了多行語句的時候,需要加上括號。有時候可以使用 fall-through 在不同的 case 裡面執行一樣的代碼。一個 fall-through 是指移除 case 語句的 “break” 然後讓下面的 case 繼續執行。當在 switch 語句裡面使用一個可枚舉的變量的時候,default 是不必要的。此外,為了避免使用默認的 case,如果新的值加入到 enum,程序員會馬上收到一個 warning 通知
Enumeration value 'ZOCEnumValue3' not handled in switch.
2.Enumerated Types 枚舉類型
當使用 enum 的時候,建議使用新的固定的基礎類型定義,因它有更強大的的類型檢查和代碼補全。 SDK 現在有一個 宏來鼓勵和促進使用固定類型定義 - NS_ENUM()
例子: *
typedef NS_ENUM(NSUInteger, ZOCMachineState) {
ZOCMachineStateNone,
ZOCMachineStateIdle,
ZOCMachineStateRunning,
ZOCMachineStatePaused
};
三、命名
1.通用的約定
盡可能遵守 Apple 的命名約定,尤其是和 內存管理規則 (NARC) 相關的地方。推薦使用長的、描述性的方法和變量名
推薦:
UIButton *settingsButton;
不推薦:
UIButton *setBut;
2.Constants 常量
常量應該使用駝峰命名法,並且為了清楚,應該用相關的類名作為前綴。
推薦:
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推薦:
static const NSTimeInterval fadeOutTime = 0.4;
常量應該盡量使用 in-line 的字符串字面值或者數字,這樣便於經常用到的時候復用,並且可以快速修改而不用查找和替換。 常量應該用 static 聲明,並且不要使用 #define,除非它就是明確作為一個宏來用的。
推薦:
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推薦:
#define CompanyName @"Apple Inc."
#define magicNumber 42
常量應該在 interface 文件中這樣被聲明:
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
並且應該在實現文件中實現它的定義。你只需要為公開的常量添加命名空間前綴。即使私有常量在實現文件中可能以不同的模式使用,你也不需要堅持這個規則了。
3.方法
對於方法簽名,在方法類型 (-/+ 符號)後應該要有一個空格。方法段之間也應該有一個空格(來符合 Apple 的規范)。在參數名稱之前總是應該有一個描述性的關鍵詞。使用“and”命名的時候應當更加謹慎。它不應該用作闡明有多個參數,比如下面的initWithWidth:height: 例子:
推薦:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推薦:
- (void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
4.字面值
NSString, NSDictionary, NSArray, 和 NSNumber 字面值應該用在任何創建不可變的實例對象。特別小心 nil 不能放進 NSArray 和 NSDictionary 裡,這會導致 Crash。
例子:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
不要這樣做:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
對於那些可變的副本,我們推薦使用明確的如 NSMutableArray, NSMutableString 這些類。
下面的例子 應該被避免:
NSMutableArray *aMutableArray = [@[] mutableCopy];
上面的書寫方式存在效率以及可讀性的問題。效率方面,一個不必要的不可變變量被創建,並且馬上被廢棄了;這並不會讓你的 App 變得更慢(除非這個方法會被很頻繁地調用),但是確實沒必要為了少打幾個字而這樣做。對於可讀性來說,存在兩個問題:第一個是當浏覽代碼並且看見 @[] 的時候你的腦海裡馬上會聯系到 NSArray 的實例,但是在這種情形下你需要停下來思考下。另一個方面,一些新手看到後可能會對可變和不可變對象的分歧感到不舒服。他/她可能對創造一個可變對象的副本不是很熟悉(當然這並不是說這個知識不重要)。當然,這並不是說存在絕對的錯誤,只是可用性(包括可讀性)有一些問題。
四、類
1.類名
類名應加上 三 個大寫字母作為前綴(兩個字母的為 Apple 的類保留)。雖然這個規范看起來難看,但是這樣做是為了減少 objective-c 沒有命名空間所帶來的問題。
一些開發者在定義 Model 對象時並不遵循這個規范(對於 Core Data 對象,我們更應該遵循這個規范)。我們建議在定義 Core Data 對象時嚴格遵循這個約定,因為你最後可能把你的 Managed Object Model 和其他(第三方庫)的 Managed Object Model 合並。你可能注意到了,這本書裡的類的前綴(其實不僅僅是類)是ZOC。另一個類的命名規范:當你創建一個子類的時候,你應該把說明性的部分放在前綴和父類名的在中間。舉個例子:如果你有一個 ZOCNetworkClient 類,子類的名字會是ZOCTwitterNetworkClient (注意 “Twitter” 在 “ZOC” 和 “NetworkClient” 之間); 按照這個約定, 一個UIViewController 的子類會是 ZOCTimelineViewController.
2.Initializer 和 dealloc 初始化
推薦的代碼組織方式:將 dealloc 方法放在實現文件的最前面(直接在 @synthesize 以及 @dynamic 之後),init 應該放在 dealloc 之後。如果有多個初始化方法, designated initializer 應該放在第一個,secondary initializer 在之後緊隨,這樣邏輯性更好。 如今有了 ARC,dealloc 方法幾乎不需要實現,不過把 init 和 dealloc 放在一起可以從視覺上強調它們是一對的。通常,在 init 方法中做的事情需要在 dealloc 方法中撤銷。
init 方法應該是這樣的結構:
- (instancetype)init
{
self = [super init]; // call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
為什麼設置 self 為 [super init] 的返回值,以及中間發生了什麼呢?這是一個十分有趣的話題。
讓我們後退一步:我們曾經寫了類似 [[NSObject alloc] init] 的表達式, alloc 和 init 區別慢慢褪去 。一個 Objective-C 的特性叫 兩步創建 。 這意味著申請分配內存和初始化是兩個分離的操作。
(1)、alloc表示對象分配內存,這個過程涉及分配足夠的可用內存來保存對象,寫入isa指針,初始化 retain 的計數,並且初始化所有實例變量。
init 是表示初始化對象,這意味著把對象放到了一個可用的狀態。這通常是指把對象的實例變量賦給了可用的值。
(2)、init 是表示初始化對象,這意味著把對象放到了一個可用的狀態。這通常是指把對象的實例變量賦給了可用的值。
3.Designated 和 Secondary Initializers
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 初始化方法。因為它們僅僅是調用類實現的 designated 初始化方法。一個類應該又且只有一個 designated 初始化方法,其他的初始化方法應該調用這個 designated 的初始化方法。
當定義一個新類的時候有三個不同的方式: 1. 不需要重載任何初始化函數 2. 重載 designated initializer 3. 定義一個新的 designated initializer。第一個方案是最簡單的:你不需要增加類的任何初始化邏輯,只需要依照父類的designated initializer。 當你希望提供額外的初始化邏輯的時候你可以重載 designated initializer。你只需要重載你的直接的超類的 designated initializer 並且確認你的實現調用了超類的方法。 你一個典型的例子是你創造UIViewController子類的時候重載 initWithNibName:bundle:方法。
在你希望提供你自己的初始化函數的時候,你應該遵守這三個步驟來保證正確的性:
(1)定義你的 designated initializer,確保調用了直接超類的 designated initializer
(2)重載直接超類的 designated initializer。調用你的新的 designated initializer.
(3)為新的 designated initializer 寫文檔
很多開發者忽略了後兩步,這不僅僅是一個粗心的問題,而且這樣違反了框架的規則,而且可能導致不確定的行為和bug。 讓我們看看正確的實現的例子:
@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
你應該考慮來用這兩個策略(不是互斥的):第一個是你在文檔中明確哪一個初始化方法是 designated 的,但是最好你可以用編譯器的指令 attribute((objc_designated_initializer)) 來標記你的意圖。 用這個編譯指令的時候,編譯器回來幫你。如果你的新的 designate initializer 沒有調用你超類的 designated initializer,上編譯器會發出警告。 然而,當沒有調用類的 designated initializer 的時候(並且依次提供必要的參數),並且調用其他父類中的 designated initialize 的時候,會變成一個不可用的狀態。參考之前的例子,當實例化一個 ZOCNewsViewController 展示一個新聞而那條新聞沒有展示的話,就會毫無意義。這個情況下你應該只需要讓其他的 designated initializer 失效,來強制調用一個非常特別的 designated initializer。通過使用另外一個編譯器指令 attribute((unavailable(“Invoke the designated initializer”))) 來修飾一個方法,通過這個屬性,會讓你在試圖調用這個方法的時候產生一個編譯錯誤。
這是之前的例子相關的實現的頭文件(這裡使用宏來讓代碼沒有那麼啰嗦)
@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
4.instancetype
一個相關的返回類型可以明確地規定用 instancetype 關鍵字作為返回類型,並且它可以在一些工廠方法或者構造器方法的場景下很有用。它可以提示編譯器正確地檢查類型,並且更加重要的是,這同時適用於它的子類。
@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name;
@end
雖然如此,根據 clang 的定義,id 可以被編譯器提升到 instancetype 。在 alloc 或者 init 中,我們強烈建議對所有返回類的實例的類方法和實例方法使用 instancetype 類型。
5.初始化模式
5.1 類簇 (class cluster)
我們的經驗是使用類簇可以幫助移除很多條件語句。
一個經典的例子是如果你有為 iPad 和 iPhone 寫的一樣的 UIViewController 子類,但是在不同的設備上有不同的行為。
@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
之前的代碼的例子展示了如何創建一個類簇。首先,[self isMemberOfClass:ZOCKintsugiPhotoViewController.class] 來避免在子類中重載初始化方法,來避免無限的遞歸。當 [[ZOCKintsugiPhotoViewController alloc] initWithPhotos:photos] 得到調用的時候之前的檢查會變成 true 的,self = nil 是用來移除所有到 ZOCKintsugiPhotoViewController 實例的引用的,它會被釋放,按照這個邏輯來檢查哪個類應該被初始化。 讓我們假設在 iPhone 上運行了這個代碼, ZOCKintsugiPhotoViewController_iPhone 沒有重載initWithPhotos:,在這個情況下,當執行 self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos]; 的時候,ZOCKintsugiPhotoViewController 會被調用,並且當第一次檢查的時候,這樣不會讓 ZOCKintsugiPhotoViewController 檢查會變成 false 調用return [super initWithNibName:nil bundle:nil]; ,這會讓 繼續初始化執行正確的初始化之前的會話。
5.2 單例
如果可能,請盡量避免使用單例而是依賴注入。 然而,如果一定要用,請使用一個線程安全的模式來創建共享的實例。 對於GCD,用 dispatch_once() 函數就可以咯。
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
6.屬性
屬性應該盡可能描述性地命名,避免縮寫,並且是小寫字母開頭的駝峰命名
例子 :
NSString *text;
不要這樣 :
NSString* text;
NSString * text;
你總應該用 getter 和 setter 因為:
(1)使用 setter 會遵守定義的內存管理語義(strong, weak, copy etc…) 這回定義更多相關的在ARC是錢,因為它始終是相關的。舉個例子,copy 每個時候你用 setter 並且傳送數據的時候,它會復制數據而不用額外的操作
(2)KVO 通知(willChangeValueForKey, didChangeValueForKey) 會被自動執行
(3)更容易debug:你可以設置一個斷點在屬性聲明上並且斷點會在每次 getter / setter 方法調用的時候執行,或者你可以在自己的自定義 setter/getter 設置斷點。
(4)允許在一個單獨的地方為設置值添加額外的邏輯。
你應該傾向於用 getter:
(1)它是對未來的變化有擴展能力的(比如,屬性是自動生成的)
(2)它允許子類化
(3)更簡單的debug(比如,允許拿出一個斷點在 getter 方法裡面,並且看誰訪問了特別的 getter
(4)它讓意圖更加清晰和明確:通過訪問 ivar _anIvar 你可以明確的訪問 self->_anIvar.這可能導致問題。在 block 裡面訪問 ivar (你捕捉並且 retain 了 sefl 即使你沒有明確的看到 self 關鍵詞)
(5)它自動產生KVO 通知
(6)在消息發送的時候增加的開銷是微不足道的。
你永遠不能在 init (以及其他初始化函數)裡面用 getter 和 setter 方法,並且你直接訪問實例變量。事實上一個子類可以重載sette或者getter並且嘗試調用其他方法,訪問屬性的或者 ivar 的話,他們可能沒有完全初始化。記住一個對象是僅僅在 init 返回的時候,才會被認為是初始化完成到一個狀態了。同樣在 dealloc 方法中(在 dealloc 方法中,一個對象可以在一個 不確定的狀態中)這是同樣需要被注意的。
當使用 setter getter 方法的時候盡量使用點符號。應該總是用點符號來訪問以及設置屬性
例子:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不要這樣:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
7.屬性定義
推薦按照下面的格式來定義屬性
@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;
為了避免 @synthesize 的使用,在實現文件中,Xcode已經自動幫你添加了。
私有屬性應該在類實現文件的類拓展(class extensions,沒有名字的 categories 中)中。有名字的 categories(如果 ZOCPrivate)不應該使用,除非拓展另外的類。
例子:
@interface ZOCViewController ()
@property (nonatomic, strong) UIView *bannerView;
@end
8.可變對象
任何可以用來用一個可變的對象設置的((比如 NSString,NSArray,NSURLRequest))屬性的的內存管理類型必須是 copy 的。這個是用來確保包裝,並且在對象不知道的情況下避免改變值。你應該同時避免暴露在公開的接口中可變的對象,因為這允許你的類的使用者改變你自己的內部表示並且破壞了封裝。你可以提供可以只讀的屬性來返回你對象的不可變的副本。
/* .h */
@property (nonatomic, readonly) NSArray *elements
/* .m */
- (NSArray *)elements {
return [self.mutableElements copy];
}
9.懶加載
我們可以選擇使用重載屬性的 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;
}
10.方法
a.參數斷言
你的方法可能要求一些參數來滿足特定的條件(比如不能為nil),在這種情況下啊最好使用 NSParameterAssert() 來斷言條件是否成立或是拋出一個異常。
b.私有方法
永遠不要在你的私有方法前加上 _ 前綴。這個前綴是 Apple 保留的。不要冒重載蘋果的私有方法的險。
11.相等性
你需要同時實現isEqual and the hash方法。如果兩個對象是被isEqual認為相等的,它們的 hash 方法需要返回一樣的值。但是如果 hash 返回一樣的值,並不能確保他們相等。這個約定是因為當被存儲在集合(如 NSDictionary 和 NSSet 在底層使用 hash 表數據的數據結構)的時候,如何查找這些對象。一定要注意 hash 方法不能返回一個常量。這是一個典型的錯誤並且會導致嚴重的問題,因為使用了這個值作為 hash 表的 key,會導致 hash 表 100%的碰撞。你總是應該用 isEqualTo<#class-name-without-prefix#>: 這樣的格式實現一個相等性檢查方法。如果你這樣做,會優先調用這個方法來避免上面的類型檢查。
一個完整的 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 值,或者,最好保證對象是不可變的。