文章目錄
- 一、通過委托與數據源協議進行對象間通信
- 1.委托模式
- 2.要點
- 二、將類的實現代碼分散到便于管理的數個分類之中
- 1.如何實現
- 2.要點
- 三、總是為第三方類的分類名稱加前綴
- 1.為什么總是為第三方類的分類名稱加前綴
- 2.要點
- 三、勿在分類中聲明屬性
- 1.勿在分類中聲明屬性的原因
- 2.要點
- 四、使用“class-continuation分類”隱藏實現細節
- 1.什么是class-continuation分類
- 2.“class-continuation分類”的合理用法
- 3.要點
- 五、通過協議提供匿名對象
- 1.什么是匿名對象
- 2.如何使用
- 3.要點
一、通過委托與數據源協議進行對象間通信
1.委托模式
對象之間經常需要相互 通信,而通信方式有很多種。OC開發者廣泛使用一種“委托模式”的編程設計模式來實現對象間的通信,該模式的主旨是:定義一套接口,某對象若想接收另一個對象的委托,則需遵從此接口,以便于成為其“委托對象”,而這“另一個對象”則可以給委托對象回傳一些信息,也可以在發生相關事件時通知委托對象。
此模式可將數據與業務邏輯解藕。比如說,用戶界面里有個現實一系列數據所用的視圖,那么,此視圖只應包含顯示數據所需的邏輯代碼,而不應決定要顯示和中數據以及數據之間如何交互等問題。視圖對象的屬性中,可以包含負責數據與事件處理的對象。這兩種對象分別稱為:“數據源”與“委托”。
@protocol EOCNetworkFetcherDelegate
- (void) networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
- (void) networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
@end
有了這個協議之后,類就可以用一個屬性來存放其委托對象了。在本例總,這個類就是EOCNetworkFetcher類。此類的接口可以寫成這樣:
@interface EOCNetworkFetcher : NSObject
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate> delegate;
@end
這個id類型的協議屬性一定要定義成weak,而非strong,因為兩者之間必須為“非擁有關系”。一般情況下,扮演delegate的那個對象也要持有本對象,直到用完本對象之后,才會釋放。
假如聲明屬性的時候用strong將本對象與委托對象之間定為“擁有關系”,那么就會引入“保留環”。因此, 本類中存放委托對象的這個屬性要么定義為weak,要么定義為unsafe_unretained。如果需要在相關對象銷毀時自動清空,則定義為前者,若不需要自動清空,則定義為后者。
某類若要遵從委托協議,可以在其接口中聲明,也可以在“分類”中聲明。如果要象外界公布此類實現了某協議,那么就在接口中聲明,而如果這個協議是個委托協議的話,那么通常只會在類的內部使用。所以說,這種情況一般都是在“分類”里聲明的:
@implementation EOCDataModel () <EOCNetworkFetcherDelegate>
@end
@implementation EOCDataModel
- (void) networkFetcher: (EOCNetworkFetcher *)fetcher didReceiveData: (NSData *) data {/* Handle data */
}
- (void) networkFetcher: (EOCNetworkFetcher *)fetcehr didFailWithError: (NSError *)error {/* Handle error */;
}
@end
委托協議中的方法一般都是“可選的”,因為扮演“受委托者”角色的這個對象未必關心其中的所有方法。這時我們就可以使用@optional關鍵字來標注其大部分或全部的方法:
@protocol EOCNetworkFetcherDelegate
@optional
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError: (NSError *)error;
@end
然而在委托對象上調用可選方法時,就必須提前使用類型信息查詢方法,來判斷這個委托對象能否響應相關選擇子。以EOCNetworkFetcher為例,應該這樣寫:
NSData *data = /* data obtained from network */;
if ([_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)]) {[_delegate networkFetcher:self didReceiveData:data];
}
判斷委托對象是否實現了相關方法,如果實現了就調用,如果沒有實現就不執行任何操作(因為給nil發送消息將使if語句的值成為false)。
也可以像下方,傳入發起委托的實例,然后在delegete對象在實現相關方法時,根據傳入的實例分別執行不同的代碼子:
- (void)networkFetcher: (EOCNetworkFetcher *)fetcher didReceiveData: (NSData *)data {if (fetcher == _myFetcherA) {/* Handle data */} else if (fetcher == _myFetcherB) {/* Handle data */}
}
上面這段代碼表明,委托對象有兩個不同的“網絡數據獲取器”,所以他們必須根據所傳的參數判斷到底是哪個EOCNetworkFetcher獲取到了數據。若沒有此信息,則委托對像在同一時間只能使用一個網絡請求獲取器,這么做不太好。
delegate里的方法也可以用于從獲取委托對象中獲取信息。比方說,EOCNetworkFetcher類也許想提供一種機制:在獲取數據的時候如果遇到了“重定向”,那么將詢問其委托對象是否應該發生重定向。delegate對象中的相關方法也可以寫成這樣:
- (BOOL)networkFetcher: (EOCNetworkFetcher *)fetcher shouldFollowRedirectToURL: (NSURL *) url;
用結構體緩存委托對象是否能響應特定的選擇子。實現緩存功能所用的代碼可以寫在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:)];
}
2.要點
- 委托模式為對象提供了一套接口,使其可由此將相關事件告知其他對象。
- 將委托對象應該支持的接口定義成協議,在協議中把可能需要處理的事件定義成方法。
- 當某對象需要從另外一個對象中獲取數據時,可以使用委托模式。這種情況下,該模式亦稱“數據源協議”
- 若有必要,可實現含有位段的結構體,將委托對象是否能響應相關協議方法這一信息緩存至其中。
二、將類的實現代碼分散到便于管理的數個分類之中
1.如何實現
OC中有一個分類機制,但是通常是使用它來補充一個需要的類,但其實還有更好的用途,那就是用來規劃我們的代碼,我們可以通過OC的“分類”機制,把類代碼按邏輯劃入幾個分區中。就像這樣:
#import <Foundation/Foundation.h>@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;- (id)initWithFirstName: (NSString *)firstName andLastName:(NSString *)lastName;/ * Friendship methods * /
- (void) addFriend:(EOCPerson *)person;
- (void) removeFriend:(EOCPerson *)person;
- (BOOL) isFriendsWith:(EOCPerson *)person;/ * Work methods * /
- (void) performDaysWork;
- (void) takeVacationFromWork;/ * Play methods * /
- (void) goToTheCinema;
- (void) goToSportsGame;@end
現在,類的實現代碼按照方法分成了好幾個部分。所以說,這項語言特性就叫做“分類”。本例中,類的基本要素(諸如屬性與初始化方法等)都聲明在“主實現”里。可是,隨著分類數量增加,當前這份實現文件很快就膨脹得無法管理了,此時就可以把每個分類提取到各自的文件中去,以EOCPerson為例,可以按照其分類拆分成下列幾個文件:
EOCPerson + Friendship(.h/.m)
EOCPerson + Work(.h/.m)
EOCPerson + Play(.h/.m)
//EOCPerson + Friendship.h
#import "EOCPerson.h"@interface EOCPerson (Friendship)
- (void) addFriend:(EOCPerson *)person;
- (void) removeFriend:(EOCPerson *)person;
- (void) isFriendsWith:(EOCPerson *)person;
@end//EOCPerson + Friendship.m
#import "EOCPerson + Friendship.h"@implementation EOCPerson (Friendship)
- (void) addFriend: (EOCPerson *)person {/* ... */
}
- (void) removeFriend:(EOCPerson *)person {/* ... */
}
- (BOOL) isFriendsWith:(EOCPerson *)person {/* ... */
}@end
并且我們之前有說過私有方法的命名,通過特殊的前綴將私有方法指示出來,那么我們學了分類規劃之后,我們還可以通過創建一個分類,這個分類其中全是私有方法,通過這種方法將這些私有方法都規劃到一個類中,當然其還是的遵循之前的命名規則。
2.要點
- 使用分類機制把類的實現代碼劃分成易于管理的小塊。
- 將應該視為“私有”的方法歸入名叫Private的分類中,以隱藏實現細節。
三、總是為第三方類的分類名稱加前綴
1.為什么總是為第三方類的分類名稱加前綴
分類機制通常用 于向無源碼的既有類中新增功能。這個特性極為強大,但在使用時也很 容易忽視其中可能產生的問題。這個問題在于:分類中的方法是直接添加在類里面的,它們 就好比這個類中的固有方法。將分類方法加人類中這一操作是在運行期系統加載分類時完成 的。運行期系統會把分類中所實現的每個方法都加人類的方法列表中。如果類中本來就有此 方法,而分類又實現了一次,那么分類中的方法會覆蓋原來那一份實現代碼。實際上可能會 發生很多次覆蓋,比如某個分類中的方法覆蓋了“ 主實現” 中的相關方法,而另外一個分類 中的方法又覆蓋了這個分類中的方法。多次覆蓋的結果以最后 一個分類為準。
比方說,要給NSString 添加分類,并在其中提供一些輔助方法,用于處理與HTTP URL 有關的字符串。你可能會把分類寫成這樣:
@interface NSString (HTTP)//Encode a string with URL encoding
- (NSString *)urlEncodeString;//Decode a URL encoded string
- (NSString *)urlDecodedString;@end
現在看起來沒什么問題,可是,如果還有 一個分類也往NSString里添加方法,那會如何呢?那個分類里可能也有個名叫urIEncodedstring的方法,其代碼與你所添加的大同小 異,但卻不能正確實現你所需的功能。那個分類的加載時機如果晚于你所寫的這個分類,那 么其代碼就會把你的那 一份覆蓋掉,這樣的話,你在代碼中調用urlEncodedstring 方法時,實際執行的是那個分類里的實現代碼。由于其執行結果和你預期的值不同所以自己所寫 的那些代碼也許就無法正常運行了。這種bug很難追查因為你可能意識不到實際執行的 urlEncodedString代碼并不是自己實現的那一份。
要解決此問題,**一般的做法是:以命名空間來區別各個分類的名稱與其中所定義的方法。 想在Objective-C中實現命名空問功能,只有 一個辦法,就是給相關名稱都加上某個共用的 前綴。與給類名加前級時所應考慮的因素相似,給分類所加的前綴也要選 得怡當才行。**一般來說,這個前級應該與應用程序或程序庫中其他地方所用的前綴相同。 于 是,我們可以給剛才那個NSString分類加上ABC前綴:
@interface NSString (ABC_HTTP)//Encode a string with URL encoding
- (NSString *)abc_urlEncodedString;//Decode a URL encoded string
- (NSString *)abc_urlDecodedString;@end
2.要點
- 向第三方類中添加分類時,總應給其名稱加上你專用的前綴
- 向第三方類中添加分類時,總應給其中的方法名加上你專用的前綴
三、勿在分類中聲明屬性
1.勿在分類中聲明屬性的原因
屬性是封裝數據的方式。盡管從技術上說,分類里也可以聲明屬性,但這種做法還是要盡量避免。原因在于,除了“class-continuation分類 ” 之 外 , 其 他分類都無法向類中新增實例變量,因此,它們無法把實現屬性所需的實例變量合成出來。
所以你要是想在分類中實現屬性就得自己進行設置該屬性的存取方法。你就可以用@dynamic或者消息轉發機制來實現其存取方法,但是還是不太提倡那樣做的。
當然,除了上述的方法可以實現設置分類中屬性的存取方法之外,還有關聯對象也可以解決在分類中不能合成實例變量的問題。就像這樣:
這樣做可行,但是不太理想,要把相似的代碼寫很多遍,而且內存管理問題上容易出錯,因為我們在為屬性實現存取方法時,經常會忘記遵從其內存管理語義。
正確的做法應該是:把所有屬性定義在主接口里。類所封裝的全部數據都應該定義在主接口中,這是唯一能夠實現實例變量的地方。并且分類機制,它的目的就是用于擴展類的功能,而非封裝數據。
有時屬性可讀也可以定義在分類中,我們可以用其get方法獲取相關的信息,但是這樣的話還不如直接定義一個方法多好,用處還多,所以說,分類還是好好使用其擴展功能吧。
2.要點
- 把封裝數據所用的全部屬性都定義在主接又里。
- 在“class-continuation分類”之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。
四、使用“class-continuation分類”隱藏實現細節
1.什么是class-continuation分類
OC動態消息系統的工作方式決定了其不可能實現真正的私有方法或者私有實例變量。那么怎么實現私有變量和私有方法呢?這就要用到特殊的“class-continuation分類”了。
“class-continuation分類”和普通的分類不同,他必須定義在其所接續的那個類的實現文件里,并且這個類沒有名字。
@interface EOCPerson ()
// Methods here
@end
這樣你就可以在其中定義你的私有方法和私有變量了,這樣有什么好處呢?公共接口里本來就能定義實例變量。不過,把它們定義在“class-continuation分類”或“實現塊”中可以將其隱藏起來,只供本類使用。這些實例變量也并非真的私有,因為在運行期總可以調用某些方法繞過此限制,不過,從一般意義上來說,他們還是私有的。此外,由于沒有聲明在公共頭文件里,所以將代碼作為程序庫的一部分來發行時,其隱藏程度更好。
2.“class-continuation分類”的合理用法
“class-continuation分類”還有一種合理用法,就是將public接口中聲明為“只讀”的屬性擴展為“可讀寫”,以便在類的內部設置其值。
就是說,你在外部.h文件中定義一個“只讀”的屬性,然后你又在“class-continuation分類”將其的“只讀”屬性改為“可讀寫”的,那么這樣下來,在外部看來他就是一個“只讀”的屬性,但是你可以在其內部自定義的設置其值了,他在內部來說就是“可讀寫”的了。
這樣做很有用,既能令外界無法修改對象,又能在其內部按照需要管理其數據。這樣,封裝在類中的數據就由實例本身來控制,而外部代碼則無法修改其值。
還有一種用法:若對象所遵從的協議只應視為私有,則可在“class-continuation分類”中聲明名,這樣就不會泄漏我們所遵從的協議
#import "EOCPerson.h"
#import "EOCSecretDelegate.h"@interface EOCPerson () <EOCSecretDelegate>
@end@implementation EOCPerson
/* ... */
@end
3.要點
- 通過“class-continuation分類”向類中新增實例變量。
- 如果某屬性在主接口中聲明為“只讀”,而類的內部又要用設置方法修改此屬性,那么就在“class-continuation分類”中將其擴展為“可讀寫”。
- 把私有方法的原型聲明在“class-continuation分類”里面。
- 若想使類所遵循的協議不為人所知,則可于“class-continuation分類”中聲明。
五、通過協議提供匿名對象
1.什么是匿名對象
若是接口背后有多個不同的實現類,而你又不想指明具體使用哪個類,那么可以考慮用這個方法——因為有時候這些類可能會變,有時候他們又無法容納于標準的類繼承體系中,因而不能以某個公共基類來統一表示。此概念通常稱為“匿名對象”。
@property (nonatomic, weak) id<EOCDelegete> delegate;
這個delegate就是“匿名的”,因為當你調用這個delegate的時候你并不知道它指的是那個類,而你卻又能使用它所指代類的方法,這就把那個類給隱藏起來了,匿名對象也是同樣的原理。
因為你可能定義很多的類,但是我們不能將它們都繼承于同一個類,并且在OC中只有id類型可以將這些類的隨便一個類都返回,所以我們在使用匿名對象的時候一定是返回的id類型。比如:我們將所有數據庫都具備的那些方法放到協議中,令返回的對象遵從此協議。
2.如何使用
先定義一個協議其中包括數據庫都有的方法:
@protocol EOCDatabaseConnection
- (void)connect;
- (void)disconnect;
- (BOOL)isConneceted;
- (NSArray *)performQuery:(NSString *)query;
@end
提供一個單例接口:
#import <Foundation/Foundation.h>@protocol EOCDatabaseConnection;@interface EOCDatabaseConnection;
+ (id)sharedInstance;
- (id<EOCDatabaseConnection>)connectionWithIdentifier: (NSString *)identifier;@end
這樣的話,處理數據庫連接的類名稱就不會暴露了,來自不同框架的那些類限制就都可以使用同一個方法來返回了,而不用對每個類都寫一個這種協議。
3.要點
- 協議可在某種程度上提供匿名類型。具體的對象類型可以淡化成遵從某協議的id類型,協議里規定了對象所實現的方法。
- 使用匿名對象來隱藏類型名稱(或類名 )。
- 如果具體類型不重要,重要的是對象能夠相應(定義在協議里的)特定方法,那么可使用匿名對象來表示。