【Effective Objective-C 2.0讀書筆記】第四章:協議和分類

Objective-C中的“協議”(protocal)類似於java中的接口。由於Objective-C不支持多重繼承,因此我們把某個類應該實現的一些列方法定義在協議裏。協議最爲常見的用途是實現委託模式。不過也有其他用法。理解並善用協議,可以令代碼更易維護,因爲協議這種方式能很好地描述接口。

“分類”(category)也是Objective-C中一項重要的語言特性。利用分類機制,可以無須繼承子類即可直接爲當前類添加方法,而在其他編程語言中,則需要通過繼承子類來實現。由於Objective-C運行期系統是高度動態的,故才能支持這一特性。然而,其中也隱藏一些陷阱,因此在使用分類前,應該先理解它。

第23條:通過委託與數據源協議進行對象間通信

對象之間經常需要相互通信,而通信方式有多種。Objective-C開發者廣泛使用“委託模式”(Delegate pattern)的編程設計模式來實現對象間的通信,該模式的主旨是:定義一套接口,某對象若想接受另一個對象的委託,則需遵從此接口,以便成爲其委託對象。而此處的“另一個對象”則可以向委託對象回傳信息,也可以在發生相關事件時通知委託對象。

此模式可將數據與業務邏輯解耦,例如視圖對象的屬性中,可以包含負責數據與事件處理的對象,分別稱爲“數據源”(data source)和“委託”(delegate)。在Objective-C中,一般通過“協議”來實現該模式,整個Cocoa系統框架都是這麼做的。如果你的代碼也這麼寫,就能與系統框架很好地融合在一起。

委託協議中的方法一般都是可選的,因爲扮演“受委託者”角色的這個對象未必關心其中的所有方法。爲了指明可選方法,經常使用@optional關鍵字來標註。

如果要在委託對象上調用可選方法,那麼必須提前使用類型信息查詢方法來判斷其是否能夠響應相關選擇子:

if([_delegate respondsToSelector: @selector(methodName:)]) {
    [_delegate methodName:parameters];
}

很容易用代碼查出某個委託對象是否能響應特定的選擇子,可是如果頻繁執行此操作,那麼除了第一次檢測結果有用,後續的檢測可能都是多餘的。有鑑於此,我們通常把委託對象能否響應某個協議方法這一信息緩存起來,以優化程序效率。將方法響應能力緩存起來的最佳途徑是使用“位段”(bitfield)數據類型。這是一項乏人問津的C語言特性,但在此處用起來卻正爲合適。

以筆者自定義的網絡數據獲取器類爲例,可以嵌入一個含有位段的結構體作爲其實例變量,而結構體的每個位段則表示delegate對象是否實現了協議中的相關方法。

@interface EOCNetworkFetcher () {
    struct {
        unsigned int didReceiveData      : 1;
        unsigned int didFailWithError    : 1;
        unsigned int didUpdateProgressTo : 1;
    } _delegateFlags;
}
@end

上述代碼中的結構體用來緩存委託對象能否響應特定的選擇子,實現緩存功能的代碼可以寫在delegate屬性所對應的設置方法裏:

- (void)setDelegate: (id<EOCNetworkFetcher>) delegate {
    _delegate = delegate;
    _delegateFlags.didReceiveData = [_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)];
    _delegateFlags.didFailWithError = [_delegate respondsToSelector: @selector(networkFetcher:didFailWithError:)];
    _delegateFlags.didUpdateProgressTo = [_delegate respondsToSelector: @selector(networkFetcher:didUpdateProgressTo:)];
}

這樣,每次調用delegate的相關方法之前,就不需要檢測委託對象是否能響應相關選擇子,而是直接查詢結構體裏的標誌。

if(_delegateFlags.didUpdateProgressTo) {
    [_delegate networkFetcher:self didUpdateProgressTo:currentProgress];
}

是否需要這種優化,應根據具體代碼來定。這就需要分析代碼性能,找出瓶頸,看這種優化技術是否能夠較大地提高程序效率。

要點:

委託模式爲對象提供了一套接口,使其可由此將相關事件告知其他對象。

當某個對象需要從另外一個對象中獲取數據時,可以使用委託模式。這種情景下,該模式亦稱“數據源協議”(data source protocal)。

若有必要,可實現含有位段的結構體,將委託對象能否響應相關協議方法這一信息緩存至其中。

第24條:將類的實現代碼分散到便於管理的若干個分類之中

通過Objective-C的“分類”(category)機制,把類代碼按邏輯劃入若干個分區中,這對開發與調試都有好處。

使用分類機制,可以把類的實現代碼劃分成易於管理的小塊。

分類機制爲調試提供了便利:對於某個分類中的所有方法來說,在調試器的回溯信息裏,分類名稱都會出現在其符號中。

將應該視爲“私有”的方法歸入名爲Private的分類中,以隱藏實現細節。

第25條:總是爲第三方類的分類名稱加前綴

分類常用於對那些你不能擁有其源代碼的已存在的類,增添新的功能。這是一個很強大的功能,但人們經常會忽略一個問題:在分類於運行時中被加載時,分類中的方法被添加到類的方法列表中,正如類自身的一部分;之後加載的分類中的方法會覆蓋先前加載的分類中的同名方法,最後覆蓋主類實現代碼的同名方法。

克服這個問題的典型方法是爲分類名稱和方法名稱添加命名空間。在Objective-C中添加命名空間的唯一方式就是在名稱前面添加前綴。例如:

@interface NSString (ABC_HTTP)

// Encode a string with URL encoding
- (NSString*)abc_urlEncodedString;

// Decode a URL encoded string
- (NSString*)abc_urlDecodedString;

@end

第26條:勿在分類中聲明屬性

屬性是封裝數據的一種方式。儘管在分類中聲明屬性在技術上來說是可行的,但是你應該避免這樣做。原因在於不能在分類中添加實例變量,除非是在class-continuation分類中。

要點:

把封裝數據所用的全部屬性都定義在主接口裏。

在“class-continuation分類”之外的其他分類中,可以定義存取方法,但儘量不要定義屬性。

第27條:使用“class-continuation分類”隱藏實現細節

在開發中,經常需要將一些實例變量和方法設置成私有的,以避免將它們直接暴露給用戶。但是鑑於Objective-C的動態消息機制,我們無法實現實例變量和方法的真正私有化。儘管可以在公共接口中將它們聲明爲private,但仍然泄露了其實現細節。使用class-continuation分類可以將實例變量和方法聲明爲私有。

class-continuation分類指的是在類的實現文件中定義的特殊分類。它是唯一可以在其中聲明實例變量的分類,並且沒有專門針對它的實現文件,在其中聲明的方法都將出現在類的主實現裏。不像其他分類,它沒有名字。

在class-continuation分類和主實現裏都可以聲明實例變量,只在內部可知,向用戶隱藏實現細節,用法如下:

@interface EOCClass () {
    NSString _anInstanceVariable;
}
// Method declarations here
@end

@implementaion EOCClass {
    int _anotherInstanceVariable;
}
// Method implementations here
@end

使用class-continuation分類的一個特別有用的地方是使用Objective-C++一起混合編程時。Objective-C與C++混合編程的場景有很多:爲了方便移植,遊戲後端代碼一般都採用C++來編寫;另外,當使用擁有C++綁定的第三方庫時,也需要採用C++。但需要注意的是,當你在類的頭文件中include或import一個C++的頭文件時,該類的實現文件就不能爲.m文件擴展後綴了,而必須是.mm後綴,即要改成Objective-C++源文件。如果持續這樣處理其他類,則整個應用程序都變成Objective-C++的了。避免這種情況發生的方法是在class-continuation分類中導入C++頭文件,這樣可以保證該類的頭文件是純Objective-C代碼。

使用class-continuation分類的另一個好處是當在公共接口中將某屬性聲明爲readonly後,可以在class-continuation分類裏面將該屬性改爲可讀(readwrite)。出現在class-continuation分類、其他分類或者類接口中的同名屬性都必須擁有相同的特性,唯一一個特例是可以將屬性從readonly狀態更改爲readwrite

使用class-continuation分類的另一個有用的地方是聲明只在類的實現裏被使用的私有方法,用法如下:

@interface EOCClass
- (void)p_privateMethod;
@end

最後,class-continuation分類還適於聲明類遵從那些被認爲是私有的協議,這樣就不會向外部顯式泄漏關於這些協議的相關信息,用法如下:

#import "EOCClass.h"
#import "EOCSecretDelegate.h"

@interface EOCClass ()<EOCSecretDelegate>
@end

@implementation EOCClass
/*...*/
@end

要點:

通過“class-continuation分類”向類中新增實例變量。

如果某屬性在主接口中聲明爲“只讀”,而類的內部又要用設置方法修改此屬性,那麼就在“class-continuation分類”中將其擴展爲“可讀寫”。

把私有方法的原型聲明在“class-continuation分類”裏面。

若想使類所遵循的協議不爲人所知,則可於“class-continuation分類”中聲明。

第28條:通過協議提供匿名對象

Objective-C中的匿名對象含義與其他編程語言中所指的匿名對象有所不同,其他編程語言中的匿名對象指的是在不提供類名的情況下來創建inline類。

協議可以被用來聲明匿名對象的接口,也就是事先不知道其類型的對象的接口。

要點:

協議可在某種程度上提供匿名類型,形如@property (nonatomic, weak) id<EOCDelegate>delegate;,隱藏了具體的類名。具體 對象類型可以淡化成遵從某協議的id類型,協議裏規定了對象所應實現的方法。

使用匿名對象來隱藏類型名稱(或類名)。

如果具體類型不重要,重要的是對象能夠響應(定義在協議裏的)特定方法,那麼可使用匿名對象來表示。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章