前言
何為協(xié)議霹购,簡(jiǎn)單來(lái)說(shuō)在OC中我們使用關(guān)鍵字@protocol
可以聲明一個(gè)協(xié)議访惜,并在協(xié)議中添加多個(gè)屬性宦芦、方法供于遵循者實(shí)現(xiàn)宙址,從某個(gè)角度上來(lái)說(shuō),這是一種不同于category
機(jī)制的category
调卑。在日常開(kāi)發(fā)中抡砂,協(xié)議可謂無(wú)處不在大咱,最為核心的UITableView
通過(guò)協(xié)議來(lái)獲取數(shù)據(jù)、完成事件處理等注益。下面就是一個(gè)最粗淺的協(xié)議
@protocol CustomProtocol
- (void)doSomething;
@end
對(duì)于協(xié)議的理解碴巾,很多的開(kāi)發(fā)者依舊保留在委托-代理
等于協(xié)議
等認(rèn)知上。然而前者依賴于后者的實(shí)現(xiàn)丑搔,而后者即便不通過(guò)前者也能完成抽象解耦的工作厦瓢。在繼續(xù)談協(xié)議可以完成的工作之前,有必要來(lái)理解一下何為協(xié)議:
協(xié)議指定了一套行為規(guī)范低匙,遵循協(xié)議的類(lèi)必須實(shí)現(xiàn)對(duì)應(yīng)的行為
協(xié)議應(yīng)用
代理回調(diào)
開(kāi)發(fā)中我們幾乎都會(huì)寫(xiě)的代碼一定是UITableView
系列的代理和數(shù)據(jù)源方法旷痕。毫無(wú)疑問(wèn),蘋(píng)果提供的這個(gè)視圖是如此的優(yōu)雅而強(qiáng)大顽冶,即便在現(xiàn)在這個(gè)數(shù)據(jù)源方法因代碼過(guò)多被瘋狂吐槽的年代欺抗,你依然無(wú)法想到其他實(shí)現(xiàn)UITableView
的更佳實(shí)踐,這個(gè)控件充分向我們展示了委托-代理
的強(qiáng)大强重。
協(xié)議最簡(jiǎn)單直觀的應(yīng)用是
委托-代理
設(shè)計(jì)模式绞呈,在封裝自定義控件的時(shí)候,我喜歡使用自定義的協(xié)議來(lái)完成用戶點(diǎn)擊等業(yè)務(wù)處理间景。個(gè)人認(rèn)為佃声,如果你想要了解代理
這一模式,起碼要自定義過(guò)自己的代理協(xié)議:
@protocol SegmentControlDelegate
@optional
- (void)segmentControl: (SegmentControl *)segmentControl didSelectItemAtIndex: (NSUInteger)index;
@end
@interface SegmentControl: UIView
@property (nonatomic, weak) id<SegmentControlDelegate> delegate;
@end
@implementation SegmentControl
- (voice)clickItem: (UIButton *)item
{
if ([_delegate respondsToSelector: @selector(segmentControl:didSelectItemAtInex:)]) {
[_delegate segmentControl: self didSelectItemAtIndex: item.tag];
}
}
@end
上面是我曾經(jīng)自定義過(guò)的分段控制器的代理偽實(shí)現(xiàn)代碼倘要,對(duì)于項(xiàng)目開(kāi)發(fā)而言圾亏,代理這種回調(diào)機(jī)制的好處包括不僅于接口目的性強(qiáng)、易于追溯調(diào)試
等封拧。
什么時(shí)候用代理
這里不免就要提到另一個(gè)跟Delegate
同樣實(shí)用受歡迎的機(jī)制Block
志鹃。比如常用的分享功能通常使用block
進(jìn)行結(jié)果回調(diào):
typedef NS_ENUM(NSInteger, ShareResult) {
ShareResultSuccess,
ShareResultFailed,
ShareResultCancel
};
- (void)share {
[shareManager shareWithResult: ^(ShareResult result) {
switch (result) {
case ShareResultSuccess:
//....
case ShareResultFailed:
//....
case ShareResultCancel:
//....
}
}];
}
這樣的代碼看著很緊湊簡(jiǎn)潔,如果用Delegate
來(lái)完成結(jié)果回調(diào)又是另一種感覺(jué)了:
- (void)share
{
ShareManager * shareManager = [[ShareManager alloc] initWithUrl: @"http://sindrilin.com" title: @"" content: @""];
shareManager.delegate = self;
[shareManager share];
}
- (void)shareManager: (ShareManager *)shareManager didCompleteShare: (ShareResult)result
{
switch (result) {
case ShareResultSuccess:
//....
case ShareResultFailed:
//....
case ShareResultCancel:
//....
}
}
同樣的代碼在Delegate
看著并不nice
泽西。這是什么原因呢曹铃?個(gè)人認(rèn)為原因如下:
- 由于代理沒(méi)有
Block
的捕獲上下文特性,需要傳入調(diào)用方參數(shù)捧杉。但在方法中陕见,ShareManager
并沒(méi)有任何作用 - 相較于
block
,代理將分享步驟和結(jié)果處理分到兩個(gè)地方味抖,邏輯不夠緊湊
同樣的评甜,假如上面的分享?yè)Q成網(wǎng)絡(luò)請(qǐng)求,并且要求在請(qǐng)求中同步下載進(jìn)度仔涩,單純的使用Block
可能就會(huì)變成這樣:
- (void)requestData {
[manager requestWithProgress: ^(CGFloat progress) {
NSLog(@"download progress: %g", progress);
self.progressView.progress = progress;
} complete: ^(id receiveData, NSError * error) {
if (error) {
[self showErrorWithMsg: error.description];
} else {
// handle receive data
}
}];
}
這個(gè)時(shí)候的Block
就會(huì)有些雜亂忍坷,看著頭疼。通過(guò)這種對(duì)比,我們不難看出如果你需要執(zhí)行某個(gè)任務(wù)承匣,并且在任務(wù)完成的時(shí)候執(zhí)行額外的操作時(shí),Block
的使用效果優(yōu)于代理锤悄。而如果某個(gè)事件是存在多種狀態(tài)韧骗,在每個(gè)狀態(tài)發(fā)生或者改變時(shí)需要回調(diào)的,定義一個(gè)協(xié)議來(lái)回調(diào)是更好的選擇零聚。
數(shù)據(jù)源
雖然蘋(píng)果將代理分為了數(shù)據(jù)源和代理兩種類(lèi)型袍暴,但是本質(zhì)上都?xì)w屬于委托-代理
機(jī)制。蘋(píng)果沒(méi)有介紹過(guò)兩者應(yīng)當(dāng)怎么劃分隶症,單從字面上的意思來(lái)劃分這兩者大概是這樣的:
- 數(shù)據(jù)源為控件提供了數(shù)據(jù)展示的接口
- 代理為控件提供了事件響應(yīng)的接口
這里要說(shuō)的大概就是假如我們自定義了控件的數(shù)據(jù)源政模,那么應(yīng)該什么時(shí)候調(diào)用數(shù)據(jù)源?按照UITableView
的使用來(lái)說(shuō)蚂会,當(dāng)存在下面幾種情況的時(shí)候淋样,數(shù)據(jù)源方法是不調(diào)用的:
- UITableView未添加到視圖上
- UITableView的寬高其中一個(gè)值為0
- UITableView沒(méi)設(shè)置代理
首先是前兩個(gè)問(wèn)題,作為所有視圖父類(lèi)的UIView
中開(kāi)放了一個(gè)方法didMoveToSuperview
胁住,這個(gè)方法會(huì)在當(dāng)前視圖被addSubview:
之后回調(diào)趁猴。因此我們需要重寫(xiě)這個(gè)方法并且從數(shù)據(jù)源獲取數(shù)據(jù):
@protocol CustomViewDataSource<NSObject>
- (CustomViewCell *)customView: (CustomView *)customView cellForRowAtIndexPath: (IndexPath *)indexPath;
@end
@interface CustomView
@property (nonatomic, weak) id<CustomViewDataSource> dataSource;
@end
@implementation CustomView
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
if (!_delegate) { return; }
if ( CGRectGetWidth(self.frame) == 0 || CGRectGetHeight(self.frame) == 0 ) { return; }
for (IndexPath * index in _visableIndexPaths) {
id cell = [_dataSource customView: self cellForRowAtIndexPath: index];
// configure cell
[self addSubview: cell];
}
}
@end
與didMoveToSuperview
類(lèi)似的還有didMoveToWindow
,這個(gè)方法會(huì)在前一個(gè)方法調(diào)用的前后分別調(diào)用一次彪见,適當(dāng)?shù)闹貙?xiě)這兩個(gè)方法可以盡量讓一些數(shù)據(jù)源方法調(diào)用延后儡司,從而減少了同一時(shí)間大量方法調(diào)用降低幀數(shù)的可能性。
同樣是委托-代理
機(jī)制余指,數(shù)據(jù)源的使用要比單純的代理考慮的因素多了許多捕犬。因此,數(shù)據(jù)源的使用算是協(xié)議應(yīng)用的進(jìn)一步酵镜。
協(xié)議抽象
在這里我們要聊聊面向?qū)ο缶幊?/code>碉碉,幾乎有經(jīng)驗(yàn)的開(kāi)發(fā)者都能隨口說(shuō)出
面向?qū)ο缶幊?/code>的特性:
多態(tài)、抽象笋婿、封裝
誉裆、這些特性大大的提高了猿們的生產(chǎn)力,但這種便利并不是沒(méi)有代價(jià)的缸濒,在Casa大神跳出面向思想系列就提出了繼承的缺陷足丢。
然而,這和本文有什么關(guān)系庇配?
在iPad開(kāi)發(fā)中有一個(gè)特殊的控件UISplitViewController
斩跌,它將屏幕分割成大小兩部分并同時(shí)顯示兩個(gè)控制器視圖。其中左側(cè)作為管理視圖捞慌,右側(cè)為詳細(xì)信息視圖耀鸦。如果在右側(cè)詳情視圖存在多個(gè)的情況下,左側(cè)點(diǎn)擊發(fā)生事件時(shí),可能需要一堆的配置創(chuàng)建控制器的代碼:
#import "AppDelegate.h"
#import "AddContactViewController.h"
#import "ContactDetailViewController.h"
@interface MasterViewController ()
@property (nonatomic, weak) UISplitViewController * splitViewController;
@property (nonatomic, strong) NSArray * contacts;
@end
@implementation MasterViewController
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
self.splitViewController = appDelegate.splitViewController;
}
- (void)contactDetail: (NSInteger)index
{
ContactDetailViewController * contactDetailVC = [[ContactDetailViewController alloc] init];
contactDetailVC.contact = _contacts[index];
[_splitViewController showDetailViewController: contactDetailVC sender: self];
}
- (void)addContact
{
AddContactViewController * addContactVC = [[AddContactViewController alloc] initWithNibName: @"AddContactViewController" bundle: nil];
[_splitViewController showDetailViewController: addContactVC sender: self];
}
@end
實(shí)際上需求當(dāng)中右側(cè)包含的界面包括何止十來(lái)個(gè)袖订,還不包括復(fù)雜的nib
控制器氮帐,這樣就導(dǎo)致了左側(cè)視圖MasterViewController
非常的亂。一方面洛姑,雖然只是弱指針引用上沐,但是MasterViewController
依舊需要獲取所在的splitViewController
。另一方面楞艾,太多的控制器創(chuàng)建配置代碼雷同而多余参咙。通過(guò)使用協(xié)議讓這些壞毛病變得不那么壞。對(duì)于右側(cè)的顯示控制器硫眯,定義一個(gè)用于初始化的類(lèi)構(gòu)造方法蕴侧,將右側(cè)控制器的創(chuàng)建統(tǒng)一化;以及一個(gè)配置數(shù)據(jù)的方法:
@protocol DetailViewControllerProtocol
+ (instancetype)detailViewController;
- (void)configurateWithData: (id)data;
@end
另一方面两入,為左側(cè)的控制器定義一個(gè)回調(diào)代理净宵,傳入將要顯示的右側(cè)控制器類(lèi)名以及配置數(shù)據(jù),讓splitViewController
自己來(lái)完成界面顯示:
/// MasterViewController.h
@protocol MasterViewControllerDelegate
- (void)showDetailWithClass: (Class)aClass data: (id)data;
@end
@interface MasterViewController: NSObject
@property (nonatomic, weak) id<MasterViewControllerDelegate> delegate;
@end
/// MasterViewController.m
@implementation MasterViewController- (void)contactDetail: (NSInteger)index
- (void)contactDetail: (NSInteger)index
{
[self showDetail: NSClassFromString(@"ContactDetailViewController") data: _contacts[index]];
}
- (void)addContact
{
[self showDetail: NSClassFromString(@"AddContactViewController") data: nil];
}
- (void)showDetail: (Class)aClass data: (id)data
{
if ([_delegate respondsToSelector: @selector(showDetailWithClass:data:)]) {
[_delegate showDetailWithClass: aClass data: data];
}
}
@end
改動(dòng)之后MasterViewController
只保留了數(shù)據(jù)以及右側(cè)控制器的名稱(chēng)谆刨,其余的工作交給代理人也就是UISplitViewController
來(lái)完成:
#import "MasterViewController.h"
#import "DetailViewControllerProtocol.h"
@interface SplitViewController ()<MasterViewControllerDelegate>
@end
@implementation SplitViewController
- (void)viewDidLoad
{
[super viewDidLoad];
for (UIViewController * viewController in self.viewControllers) {
if ([viewController isKindOfClass: [MasterViewController class]]) {
MasterViewController * masterVC = (MasterViewController *)viewController;
masterVC.delegate = self;
break;
}
}
}
- (void)showDetailWithClass: (Class)aClass data: (id)data
{
UIViewController<DetailViewControllerProtocol> * detailVC = [aClass detailViewController];
[detailVC configurateWithData: data];
[self showDetailViewController: detailVC sender: self];
}
@end
最后就是右側(cè)不同的控制器實(shí)現(xiàn)協(xié)議方法塘娶,完成控制器的創(chuàng)建和配置:
@implementation ContactDetailViewController
+ (instancetype)detailViewController
{
return [[[self class] alloc] init];
}
- (void)configurateWithData: (Contact *)data
{
self.phone = data.phone;
self.fullName = data.fullName;
}
@end
@implementation AddContactViewController
+ (instancetype)detailViewController
{
return [[[self class] alloc] initWithNibName: NSStringFromClass([self class) bundle: nil];
}
- (void)configurateWithData: (id)data { }
/// More. Load from storyboard etc
@end
實(shí)際上上面的例子是通過(guò)協(xié)議將不同控制器統(tǒng)一的行為(創(chuàng)建、配置)抽象成協(xié)議痊夭,從而減少了重復(fù)代碼的使用刁岸。這種抽象統(tǒng)一的好處在于耦合度低,任何控制器只要實(shí)現(xiàn)了協(xié)議就能很好的在這個(gè)splitViewController
上使用展示她我。
結(jié)束語(yǔ)
曾幾何時(shí)虹曙,在懵懂中自學(xué)iOS時(shí),總是不能理解協(xié)議的用處番舆。甚至在接觸了Block
的時(shí)候酝碳,覺(jué)得這tm的太好用了,代理有個(gè)毛卵用恨狈。然后一直濫用Block
直到發(fā)現(xiàn)并沒(méi)有那么的神疏哗,往往還會(huì)帶來(lái)隱晦的bug,于是才重新?lián)炱鹆?code>Delegate禾怠。從那之后逐漸的開(kāi)始嘗試包括通知返奉、代理、Block
多種回調(diào)方式的自定義控件吗氏,因此芽偏,本文嚴(yán)格來(lái)說(shuō)算是紀(jì)錄曾經(jīng)的一次成長(zhǎng)。
轉(zhuǎn)載請(qǐng)注明本文作者以及地址