協(xié)議(protocol)與java的接口類似刊侯。CO不支持多重繼承汁政,因而吧某個類應(yīng)該實現(xiàn)的一系列方法定義在協(xié)議里勉躺。協(xié)議最常見的用途是事先委托模式,也有其他用法瓮下。
分類(Category)是OC一項重要語言特性翰铡。利用分類機制,無需繼承子類就可以直接為當(dāng)前類添加方法讽坏。由于OC運行期系統(tǒng)是高度動態(tài)的坠陈,所以才能支持這一特性食零,也有一些坑膛薛。
23轻掩、通過委托與數(shù)據(jù)源協(xié)議進行對象間通信
委托模式(Delegate pattern):定義一套接口,某對象若想接受另一個對象的委托胀葱,則需遵從次接口漠秋,以便成為其委托對象(delegate)。而這“另一個對象”可以給其委托對象回傳一些消息抵屿,也可以在發(fā)生相關(guān)事件時通知委托對象庆锦。
此模式可將數(shù)據(jù)與業(yè)務(wù)邏輯解耦。
視圖對象中可以包含負責(zé)數(shù)據(jù)與事件處理的對象晌该,這兩種對象分別稱為數(shù)據(jù)源(data source)與委托(delegate)肥荔。
代理方法:
#import <Foundation/Foundation.h>
@class ZYDNetworkFetcher;
@protocol ZYDNetrokFetcherDelegate <NSObject>
// 一般默認方法是必選的
- (void)networkFetcherRequestFinished:(ZYDNetworkFetcher *)fetcher;
// 委托協(xié)議中的方法一般是可選的(optional)
@optional
- (void)networkFetcher:(ZYDNetworkFetcher *)fetcher didReceivdData:(NSData *)data;
- (void)networdFetcher:(ZYDNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
@end
發(fā)起委托
.h
#import <Foundation/Foundation.h>
#import "ZYDNetrokFetcherDelegate.h"
@interface ZYDNetworkFetcher : NSObject
//用weak而不是strong,兩者之間必須為非擁有關(guān)系朝群。
//想要使用ZYDNetworkFetcher對象的那個而對象會持有本對象燕耿,知道用完本對象之后,才會釋放姜胖。
//如果聲明屬性的時候用strong將本對象與委托對象之間定為擁有關(guān)系誉帅,那么就會引入保留換(retain cycle)
//所以,本類中存放委托對象的這個屬性要么定義weak 要么定義unsafe_unretained右莱。
//在相關(guān)對象銷毀時自動清空蚜锨,使用weak,不需要自動清空慢蜓,使用后者亚再。
@property (nonatomic,weak) id <ZYDNetrokFetcherDelegate> delegate;
@end
.m
#import "ZYDNetworkFetcher.h"
@implementation ZYDNetworkFetcher
- (void)requestFinishedWithData:(NSData *)data {
if ([_delegate respondsToSelector:@selector(networkFetcher:didReceivdData:)]) {
[_delegate networkFetcher:self didReceivdData:data];
}
}
- (void)reqeustFailedWithError:(NSError *)error {
if ([_delegate respondsToSelector:@selector(networdFetcher:didFailWithError:)]) {
[_delegate networdFetcher:self didFailWithError:error];
}
}
@end
委托對象
#import "ZYDDataModel.h"
#import "ZYDNetrokFetcherDelegate.h"
// 實現(xiàn)委托對象的辦法,是聲明遵從委托協(xié)議
// 然后把協(xié)議中想實現(xiàn)的方法在類里實現(xiàn)出來
@interface ZYDDataModel () <ZYDNetrokFetcherDelegate>
@end
@implementation ZYDDataModel
#pragma mark -- 實現(xiàn)協(xié)議方法
- (void)networkFetcherRequestFinished:(ZYDNetworkFetcher *)fetcher {
}
- (void)networkFetcher:(ZYDNetworkFetcher *)fetcher didReceivdData:(NSData *)data {
}
- (void)networdFetcher:(ZYDNetworkFetcher *)fetcher didFailWithError:(NSError *)error {
}
@end
數(shù)據(jù)源模式:信息從數(shù)據(jù)源流向類晨抡。
常規(guī)的委托模式氛悬,信息則從類流向受委托者。
一個類中相關(guān)方法要調(diào)用很多次的時候耘柱,需要反復(fù)調(diào)用判斷邏輯[_delegate respondsToSelector:@selector(networdFetcher:didFailWithError:)]
如捅。可以使用結(jié)構(gòu)體调煎,將結(jié)果標記緩存下來镜遣,只判斷標記,不在調(diào)用判斷語句士袄。
#import "ZYDNetworkFetcher.h"
@interface ZYDNetworkFetcher () {
struct {
unsigned int didReceiveData: 1;
unsigned int didFailWIthError : 1;
} _delegateFlags;
}
@end
@implementation ZYDNetworkFetcher
- (void)setDelegate:(id<ZYDNetrokFetcherDelegate>)delegate {
_delegate = delegate;
_delegateFlags.didReceiveData = [_delegate respondsToSelector:@selector(networkFetcher:didReceivdData:)];
_delegateFlags.didFailWIthError = [_delegate respondsToSelector:@selector(networdFetcher:didFailWithError:)];
}
- (void)requestFinishedWithData:(NSData *)data {
if (_delegateFlags.didReceiveData) {
[_delegate networkFetcher:self didReceivdData:data];
}
}
- (void)reqeustFailedWithError:(NSError *)error {
if (_delegateFlags.didFailWIthError) {
[_delegate networdFetcher:self didFailWithError:error];
}
}
@end
- 委托模式為對象提供了一套接口悲关,使其可由此將相關(guān)事件告知其他對象。
- 將委托對象應(yīng)該支持的接口定義成協(xié)議娄柳,在協(xié)議中把可能需要處理的時間定義成方法坚洽。
- 當(dāng)某對象需要從另外一個對象中獲取數(shù)據(jù)時,可以使用委托模式西土,這種情況下亦稱為數(shù)據(jù)源協(xié)議讶舰。
- 如果有必要,可實現(xiàn)含有位段的結(jié)構(gòu)體需了,將委托對象是否能響應(yīng)相關(guān)協(xié)議方法這一信息緩存至其中跳昼。
24、將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中
OC分類(類別/Category)機制肋乍,把類代碼按邏輯劃入幾個分區(qū)中鹅颊,這對開發(fā)與調(diào)試都有好處。
將代碼按照方法分成好幾個部分墓造,類的基本要輸都聲明在主實現(xiàn)文件中堪伍,執(zhí)行不同類型的操作作用的另外幾套方法歸入到各個分類中锚烦。
- 使用分類機制以后,依然可以把整個類都定義在一個接口文件中帝雇,并將其代碼寫在一個實現(xiàn)文件里涮俄。
.h
#import <Foundation/Foundation.h>
@interface ZYDPersonModel : NSObject
@property (nonatomic,copy,readonly) NSString *firstName;
@property (nonatomic,copy,readonly) NSString *lastName;
@property (nonatomic,strong,readonly) NSArray *friends;
- (instancetype)initWithFristName:(NSString *)firstName lastName:(NSString *)lastName;
@end
@interface ZYDPersonModel (FriendShip)
- (void)addFried:(ZYDPersonModel *)person;
- (void)removeFriend:(ZYDPersonModel *)person;
- (BOOL)isFriendWith:(ZYDPersonModel *)person;
@end
@interface ZYDPersonModel (Wrok)
- (void)performDaysWork;
@end
.m
#import "ZYDPersonModel.h"
@interface ZYDPersonModel ()
@property (nonatomic,readwrite) NSMutableArray *internalFriends;
@end
@implementation ZYDPersonModel
- (instancetype)initWithFristName:(NSString *)firstName lastName:(NSString *)lastName {
if (self = [super init]) {
_firstName = [firstName copy];
_lastName = [lastName copy];
_internalFriends = [NSMutableArray array];
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"name :%@ ~ %@ \n frends: %@",_firstName,_lastName,_internalFriends];
}
- (NSArray *)friends {
return [_internalFriends copy];
}
@end
@implementation ZYDPersonModel(FriendShip)
- (void)addFried:(ZYDPersonModel *)person {
[_internalFriends addObject:person];
}
- (void)removeFriend:(ZYDPersonModel *)person {
if ([_internalFriends indexOfObject:person] != NSNotFound) {
[_internalFriends removeObject:person];
}
}
- (BOOL)isFriendWith:(ZYDPersonModel *)person {
if ([_internalFriends indexOfObject:person] != NSNotFound) {
return YES;
}
return NO;
}
@end
@implementation ZYDPersonModel (Wrok)
- (void)performDaysWork {
NSLog(@"%@ %@ work on Monday",_firstName,_lastName);
}
@end
- 隨著分類數(shù)量增加,可以各個分類提取到各自的文件中尸闸。
#import "ZYDPersonModel.h"
@interface ZYDPersonModel (Play)
- (void)goToThePark;
@end
#import "ZYDPersonModel+Play.h"
@implementation ZYDPersonModel (Play)
- (void)goToThePark {
NSLog(@"%@ %@ go to the Park",self.firstName,self.lastName);
}
@end
兩種方式的區(qū)別在于彻亲,分類在一個接口文件中,只需要引入主頭文件即可#import "ZYDPersonModel.h"
吮廉。如果單獨提取到各自的文件中苞尝,使用時需要單獨引入頭文件#import "ZYDPersonModel+Play.h"
#import "ViewController.h"
#import "ZYDPersonModel.h"
#import "ZYDPersonModel+Play.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
ZYDPersonModel *personOne = [[ZYDPersonModel alloc] initWithFristName:@"Smith" lastName:@"Json"];
ZYDPersonModel *personTwo = [[ZYDPersonModel alloc] initWithFristName:@"Wellienm" lastName:@"Jone"];
[personOne addFried:personTwo];
NSLog(@"%@",personOne);
[personOne performDaysWork];
[personOne goToThePark];
}
@end
通過分類機制,可以把類代碼分成很多個易于管理的小塊宦芦。還有一個好處宙址,將代碼分割成幾塊,把相應(yīng)代碼歸入不同的功能區(qū)(functional area)调卑。
再者曼氛,將類代碼打散到分類,便于調(diào)試令野。
另外舀患,可以創(chuàng)建名為Private的分離,把一些應(yīng)該視為私有方法气破,只會在類或框架內(nèi)部使用聊浅,無需對外公開的方法收入其中。
在編寫準備分享給其他開發(fā)者使用的程序庫時现使,可以考慮創(chuàng)建Private分類低匙。如果程序庫需要用到這些方法,就引入此分類的頭文件碳锈,而分類的頭文件并不隨程序庫一并公開顽冶。
- 使用分類機制把類的實現(xiàn)代碼劃分成易于管理的小塊。
- 將應(yīng)該視為私有的方法歸入名叫Private的分類中售碳,以隱藏實現(xiàn)細節(jié)强重。
25、為第三方類的分類名稱加前綴
分類機制通常用于向無源碼的既有類中新增功能贸人。
分類的方法是直接加到類中的间景,就好比是類中固有的方法,將分類方法加入類中這一操作時在運行期系統(tǒng)加載分類時完成的艺智。運行期系統(tǒng)會把分類中所實現(xiàn)的每個方法都加入類的方法列表中倘要。
如果類中本來就有這個方法,分類中又實現(xiàn)了一次十拣,那么分類中方法會覆蓋原來的實現(xiàn)代碼封拧。有可能會發(fā)生多次覆蓋志鹃。
如果多個分類名稱相同,在運行期泽西,是不會報錯的曹铃。但是加載的分類有可能不是你所期望的。
如果有相同的方法尝苇,那么運行時調(diào)用的不一定是你想要的方法。
運行期不會報錯埠胖,但是在實現(xiàn)結(jié)果的時候糠溜,就會出現(xiàn)未知錯誤。
比如實現(xiàn)了NSString的兩個分類直撤,同時都有一個分類方法"- (NSString *)urlEncordedString;"那么在調(diào)用過程中非竿,就不知道是調(diào)用哪個分類中實現(xiàn)的方法。同樣能夠正常編譯通過谋竖。
NSString *urlString = @"http://www.baidu.com";
NSLog(@"%@",[urlString urlEncordedString]);
因為不會報錯红柱,這種問題比較難發(fā)現(xiàn),所以在寫之前就避免這種情況就顯得非常重要蓖乘。
當(dāng)然如果分類名相同锤悄,但是方法名不同時,有可能出現(xiàn)的問題是:No visible @interface for 'NSString' declares the selector 'seconString'
嘉抒,你無法調(diào)用自己實現(xiàn)的方法零聚。系統(tǒng)只是提供最后加載到的分類,如此而已些侍。
- 向第三方庫添加分類時隶症,給分類名稱加上專用前綴。
- 向第三方庫添加分類時岗宣,給分類方法名加上專用前綴蚂会。
26、 勿在分類中聲明屬性
屬性是封裝數(shù)據(jù)的形式耗式。盡管從技術(shù)上說胁住,分類里也可以聲明屬性,但這種做法還是要盡量避免刊咳。原因在于措嵌,處理實現(xiàn)文件之外,其他分類無法向類中新增實例變量芦缰。因此企巢,他們無法將實現(xiàn)屬性所需的實例變量合成出來。
分類让蕾,應(yīng)將其理解為一種手段浪规,目標在于擴展類的功能或听,而非封裝數(shù)據(jù)。
- 把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里笋婿。
- 在實現(xiàn)文件之外的其他分類中誉裆,可以定義存取方法,但盡量不要定義屬性缸濒。
???
27足丢、使用class-continuation分類隱藏實現(xiàn)細節(jié)
class-continuation分類,和普通分類不同庇配,他沒有名字斩跌,它在其所續(xù)接的那個類的實現(xiàn)文件里。重要之處在于捞慌,這是唯一能聲明實例變量的分類耀鸦,而且此分類沒有特定的實現(xiàn)文件,其中的實現(xiàn)方法都應(yīng)該定義在類的實現(xiàn)文件里啸澡。
@interface ZYDPersonModel ()
@end
在class-continuation分類中給類新增實例變量袖订,或者在實現(xiàn)塊增加,可以將其隱藏起來嗅虏,只供本類使用洛姑。
引入C++文件,一般用class-continuation分類解決皮服。
class-continuation還有一種合理用法吏口,將public接口中聲明為只讀的屬性擴展為可讀寫,以便在類的內(nèi)部設(shè)置其值冰更。通常不直接訪問實例變量产徊,而是通過設(shè)置訪問方法來做,應(yīng)為這樣能夠觸發(fā)鍵值觀察蜀细。
出現(xiàn)在class-continuation分類或其他分類中的屬性必須同類接口里的屬性劇痛相同的特質(zhì)(attribute)舟铜,不過只讀狀態(tài)可以擴充為可讀寫。
只會在累的實現(xiàn)代碼中用到的私有方法也可以聲明在class-continuation分類中奠衔。
若對象所遵從的協(xié)議只應(yīng)視為私有谆刨,則可雜class-continuation中聲明。
@interface ZYDDataModel () <ZYDNetrokFetcherDelegate>
@end
- 通過class-continuation分類向類中新增實例變量归斤。
- 如果某屬性在主接口中聲明為只讀痊夭,而類的內(nèi)部又要用設(shè)置方法修改此屬性,那么就在class-continuation分類中將其擴展為可讀寫脏里。
- 把私有方法的原型聲明在class-continuation分類里面她我。
- 若想使類所遵循的協(xié)議不為人所知,則可于class-continuation分類中聲明。
28番舆、通過協(xié)議提供匿名對象
匿名對象(anonymous object)酝碳,用協(xié)議把自己所寫的API之中的實心細節(jié)隱藏起來,將返回的對象設(shè)計為遵從此協(xié)議的純id類型恨狈。這樣疏哗,想要隱藏的類名就不會出現(xiàn)在API之中。
若是北郵有多個不同的實現(xiàn)類禾怠,不想指明具體使用哪個類返奉,可以考慮用這個方法,這些類有可能會變吗氏,有時候他們又無法容納于標準的類集成體系中芽偏,因而不能以某一個公共基類來統(tǒng)一表示。
例如牲证,在定義受委托者屬性時:@property (nonatomic,weak) id <ZYDNetrokFetcherDelegate> delegate;
任何類的對象都能充當(dāng)代理屬性哮针,即便不繼承自NSObject也可以关面,只要遵循ZYDNetrokFetcherDelegate
協(xié)議就行坦袍。對于具備此屬性的類來說,delegate就是匿名的(anonymous)等太。
- 例如捂齐,實現(xiàn)數(shù)據(jù)庫鏈接
// 通過代理實現(xiàn)方法
#import <Foundation/Foundation.h>
@protocol ZYDDatabaseConnection <NSObject>
- (void)connect;
- (void)disConnect;
- (BOOL)isConnected;
- (NSSarray *)perforQuery:(NSString *)query;
@end
通過管理器提供數(shù)據(jù)庫鏈接。
#import <Foundation/Foundation.h>
@protocol ZYDDatabaseConnection;
@interface ZYDDatabaseManager : NSObject
+ (id)sharedInstance;
//通過 此返回對象缩抡,實現(xiàn)數(shù)據(jù)庫鏈接奠宜、斷開、查詢方法瞻想,
// 對象類型并不重要压真,重要的是有沒有實現(xiàn)方法。--這種情況下也可以用這些匿名類型(anonymous type)
- (id <ZYDDatabaseConnection>)connectionWithIdentifier:(NSString *)identifier;
@end
可以創(chuàng)建匿名對象將不同的三方類庫包裹一下蘑险,是匿名對象成為其子類滴肿,并遵從ZYDDatabaseConnection
協(xié)議。然后用connectionWithIdentifier:
返回這些類對象佃迄。后續(xù)版本泼差,無須改變公共API,即可切換后端的實現(xiàn)類呵俏。
(待完善)
- 協(xié)議可以在某種程度上提供匿名類型堆缘,具體的對象類型可以淡化成遵從某協(xié)議的id類型,協(xié)議里規(guī)定了對象所應(yīng)實現(xiàn)的方法普碎。
- 使用匿名對象來隱藏類型名稱(或類名)
- 如果具體類型不重要吼肥,重要的是對象能夠相應(yīng)(定義在協(xié)議里的)特定方法,那么可以使用匿名對象來表示。