最近閱讀了《禅與 Objective-C 程式設計藝術》這篇文章,奈何英文不算太好,而且看着好累,還好找到了中文版。讀完了這篇文章,不管是對名詞的解釋、代碼規範、還是編碼技巧,都有所感悟,總結一下這篇文章需要記錄的知識點。
1.常量
推薦使用常量來代替字元串字面值和數字,這樣能夠友善複用,而且可以快速修改而不需要查找和替換。常量應該用 static 聲明為靜态常量,而不要用 #define,除非它明确的作為一個宏來使用。
推薦:
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = f;
不推薦:
#define CompanyName @"Apple Inc."
#define magicNumber 42
常量應該在頭檔案中以這樣的形式暴露給外部:
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
并在實作檔案中為它指派。
2.初始化
我們常常寫 [[NSObject alloc] init] 這樣的代碼,Objective-C 的這個特性叫做 兩步建立。這意味着申請配置設定記憶體和初始化被分離成兩步,alloc 和 init。
- alloc 負責建立對象,這個過程包括配置設定足夠的記憶體來儲存對象,寫入 isa 指針,初始化引用計數,以及重置所有執行個體變量。
- init 負責初始化對象,這意味着使對象處于可用狀态。這通常意味着為對象的執行個體變量賦予合理有用的值。
alloc 方法将傳回一個有效的未初始化的對象執行個體。每一個對這個執行個體發送的消息會被轉換成一次 objc_msgSend() 函數的調用,形參 self 的實參是 alloc 傳回的指針;這樣 self 在所有方法的作用域内都能夠被通路。 按照慣例,為了完成兩步建立,新建立的執行個體第一個被調用的方法将是 init 方法。注意,NSObject 在實作 init 時,隻是簡單的傳回了 self。
3.id 和 instancetype 差別
- (id)array;
- (instancetype)array;
+ (id)array {
return [NSArray array];
}
+ (instancetype)array {
return [NSArray array];
}
以上代碼初始化數組,傳回值分别是 id 和 instancetype
- 傳回值為 id 得到的傳回類型是 id
- 傳回值為 instancetype 得到的傳回類型是 NSArray *
總結一下 instancetype 優點:使那些非關聯傳回類型的方法傳回所在類的類型。優點是能夠确定對象的類型,幫助編譯器更好的為我們定位代碼書寫問題。
4.isKindOfClass 和 isMemberOfClass 的差別
建立兩個類:
- Person 繼承 NSObject
- Student 繼承 Person
BOOL bool1 = [teacher isKindOfClass:[Teacher class]];
BOOL bool2 = [teacher isKindOfClass:[Person class]];
BOOL bool3 = [teacher isKindOfClass:[NSObject class]];
上面三個 BOOL 都為 YES!
BOOL bool1 = [teacher isMemberOfClass:[Teacher class]];
BOOL bool1 = [teacher isMemberOfClass:[Person class]];
BOOL bool1 = [teacher isMemberOfClass:[NSObject class]];
上面第一個 BOOL 為 YES , 其餘兩個為 NO
是以 isKindOfClass 來确定一個對象是否是一個類的成員,或者是派生自該類的成員;isMemberOfClass 隻能确定一個對象是否是目前類的成員
5.setter 和 getter 注意點
永遠不要在 init 方法(以及其他初始化方法)裡面用 getter 和 setter 方法,你應當直接通路執行個體變量。這樣做是為了防止有子類時,出現這樣的情況:它的子類最終重載了其 setter 或者 getter 方法,是以導緻該子類去調用其他的方法、通路那些處于不穩定狀态,或者稱為沒有初始化完成的屬性或者 ivar 。記住一個對象僅僅在 init 傳回的時候,才會被認為是達到了初始化完成的狀态。
6.深入 Block
一些關鍵點:
- block 是在棧上建立的
- block 可以複制到堆上
- block 會捕獲棧上的變量(或指針),将其複制為自己私有的const(變量)。
如果 block 沒有在其他地方被保持,那麼它會随着棧生存并且當棧幀(stack frame)傳回的時候消失。僅存在于棧上時,block對對象通路的記憶體管理和生命周期沒有任何影響。
如果 block 需要在棧幀傳回的時候存在,它們需要明确地被複制到堆上,這樣,block 會像其他 Cocoa 對象一樣增加引用計數。當它們被複制的時候,它會帶着它們的捕獲作用域一起,retain 他們所有引用的對象。
如果一個 block引用了一個棧變量或指針,那麼這個block初始化的時候會擁有這個變量或指針的const副本,是以(被捕獲之後再在棧中改變這個變量或指針的值)是不起作用的。(譯者注:是以這時候我們在block中對這種變量進行指派會編譯報錯:Variable is not assignable(missing __block type specifier),因為他們是副本而且是const的.具體見下面的例程)。
當一個 block 被複制後,__block 聲明的棧變量的引用被複制到了堆裡,複制完成之後,無論是棧上的block還是剛剛産生在堆上的block(棧上block的副本)都會引用該變量在堆上的副本。
最重要的事情是 __block 聲明的變量和指針在 block 裡面是作為顯示操作真實值/對象的結構來對待的。
block 在 Objective-C 的 runtime(運作時) 裡面被當作一等公民對待:他們有一個 isa 指針,一個類也是用 isa 指針在Objective-C 運作時來通路方法和存儲資料的。在非 ARC 環境肯定會把它搞得很糟糕,并且懸挂指針會導緻 crash。__block 僅僅對 block 内的變量起作用,它隻是簡單地告訴 block:
嗨,這個指針或者原始的類型依賴它們在的棧。請用一個棧上的新變量來引用它。我是說,請對它進行雙重解引用,不要 retain 它。 謝謝,哥們。
如果在定義之後但是 block 沒有被調用前,對象被釋放了,那麼 block 的執行會導緻 crash。 __block 變量不會在 block 中被持有,最後… 指針、引用、解引用以及引用計數變得一團糟。
7.委托和資料源
本質上,委托代理模式僅需要代理方提供一些回調方法,即代理方需要實作一系列空傳回值的方法。
不幸的是 Apple 的 API 并沒有遵守這個原則,開發者也效仿 Apple 進入了一個誤區。典型的例子就是 UITableViewDelegate 協定。
它的一些方法傳回 void 類型,就像我們所說的回調:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath;
但是其他的就不是那麼回事:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
當委托者詢問代理者一些資訊的時候,這就暗示着資訊是從代理者流向委托者而非相反的過程。這是概念性的不同,須用另一個新的名字來描述這種模式:資料源模式。
可能有人會說 Apple 有一個 UITableViewDataSouce protocol 來做這個(雖然使用委托模式的名字),但是實際上它的方法是用來提供真實的資料應該如何被展示的資訊的。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
此外,以上兩個方法 Apple 混合了展示層和資料層,這顯的非常糟糕,但是很少的開發者感到糟糕。而且我們在這裡把空傳回值和非空傳回值的方法都天真地叫做委托方法。
為了分離概念,我們應該這樣做:
委托模式(delegate pattern):事件發生的時候,委托者需要通知代理者。
資料源模式(datasource pattern):委托者需要從資料源對象拉取資料。