談談Objective-C中的協議

吳與倫發表於2019-02-27

談談Objective-C中的協議

協議簡述

協議(protocol)是oc中的一個重要的語言特性,協議中定義了一些方法,若某個類想要實現這個協議中的一系列方法,則必須遵守這個協議,這個類物件被稱為"委託物件" , 即為"代理"。這也說明這種模式是單向的,訊息的傳送方(委託方) 需要知道接收方(代理方)是誰,即只需要知道它的代理方是否遵守了協議( protocol)反過來是不需要的。

協議的兩種模式

這兩種模式分別為委託模式(delegate)和資料來源模式(dataSource),區別在於資訊的流向不同.

  • 委託模式:資訊通過協議中的方法引數從委託方流向代理方,或者事件發生時 委託者通知代理者 ,簡單表述為:委託方傳遞資訊或者事件到代理方。
  • 資料來源模式:資料來源模式的資訊流向與委託模式正好相反,委託方需要從代理方拉取資料。 簡單表述為:代理方傳遞資訊到委託方。

圖示 :(箭頭表示資訊的流向)

QQ20170720-0@2x.png

協議的作用

  • 進行物件間的相互通訊。委託模式時這個委託方可以給代理方回傳一些資訊,也可以在發生相關事件時通知委託物件.資料來源模式時, 代理方可以給委託方回傳一些資訊,這個資訊通常在代理方實現方法的返回值中。
  • 可將資料與業務邏輯解耦。在資料來源模式中,委託方只需要構建如何處理這些資料的業務邏輯程式碼,而不用關心來源,來源由遵守協議的代理方提供。在委託模式中,委託方向代理方提供資訊,代理方拿到資訊後構建相關的業務邏輯。

協議的宣告與使用

為了演示此模式,舉一個UIWebView中的UIWebViewDelegate作為例子:

@protocol UIWebViewDelegate <NSObject>

@optional

// 將NSURLRequest物件傳遞給代理方,代理方由此判斷將是否載入此網頁,並將結果返回給委託方做處理,這個方法很好的說明了協議用於物件之間的相互通訊。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
// 通知代理方網頁開始載入
- (void)webViewDidStartLoad:(UIWebView *)webView;
// 通知代理方網頁載入完成
- (void)webViewDidFinishLoad:(UIWebView *)webView;
// 通知代理方網頁載入失敗,並將NSError 物件傳遞給代理方。
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;

@end
複製程式碼
  • 宣告協議要用到@protocol 關鍵字,後面跟著協議名 ,協議如果是委託模式就在類名後面加上Delegate一詞,如果是資料來源模式就在類名後面加上DataSource一詞。整個類名採用‘‘駝峰法’’來寫。協議名後面的<>內是這個協議繼承的另一個協議,遵守此協議的委託物件也可以實現該協議的方法。協議的多繼承也很好的彌補了oc不支援多重繼承的語言缺陷。

  • 協議中的方法名要寫清楚,方法名應該能準確的傳達出委託物件實現該方法的時機和作用。

  • 有了這個協議以後,那麼就需要用一個屬性來存放委託物件了。這個屬性需定義成weak,而不是用strong,如果使用strong,委託方持有委託物件,委託物件也會持有委託方,兩個物件之間都是強應用,形成你中有我,我中有你的關係,就會形成迴圈引用(retain circle) 造成這兩個物件無法銷燬。

  • 注意協議中的@optional (可選的),宣告這個關鍵字的方法表示委託物件不需要必須實現協議中的所有方法,如UIWebViewDelegate中的webViewDidStartLoad: 協議方法,也許委託物件並不用在網頁開始載入時做任何事情,所以可以不必實現這個方法。如果要在委託物件上呼叫可選方法,那麼需要提前判斷這個委託物件能否響應相關的選擇器,需要使用型別資訊查詢方法respondsToSelector: 來判斷:

    if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
        [self.delegate webViewDidStartLoad:self];
    }
複製程式碼

這段程式碼的意思是來判斷委託物件是否實現了相關方法。如果實現了就呼叫,如果沒有實現就不執行任何操作。這樣委託物件就可以選擇性的實現相關的方法了。即使self.delegate = nil,即沒有設定委託物件的時候,程式也能照常執行,因為給nil傳送訊息將使if語句的值成為false。

  • 在協議方法中,應該總是把委託例項及發起方也一併傳入方法中,例如在UIWebViewDelegate協議中,協議方法中總是帶有UIWebView例項。這樣做的好處是委託物件在實現方法時可以區分不同的委託例項。
- (void)webViewDidStartLoad:(UIWebView *)webView{

    if(webView == self.webViewA){
        // do something;
    } else if (webView == self.webViewB){
       // do something;
    }
}
複製程式碼
  • 我將用一個控制器來載入一個webView,如果控制器要實現UIWebViewDelegate的相關協議方法,控制器必須成為webView的委託物件,即代理。

@interface ViewController ()<UIWebViewDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    UIWebView *webView = [[UIWebView alloc] init];
    webView.frame = self.view.bounds;
    [webView loadRequest:request]
    // 成為webView的委託物件,即代理。
    webView.delegate = self;
    [self.view addSubview:webView];
    
}

#pragma mark -- 根據需求選擇性的實現協議中的方法

- (void)webViewDidStartLoad:(UIWebView *)webView{
    
}

- (void)webViewDidFinishLoad:(UIWebView *)webView{
    
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
    
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    
    return YES;
}
複製程式碼

總結

  • 委託模式為某個物件提供了一套介面,這套介面被稱為協議,協議中的方法可將相關資訊或相關事件告知其他物件,這個物件必須遵守這個協議,併成為這個物件的代理。
  • 協議的作用是進行物件間的相互通訊及資料與業務邏輯解耦。

參考資料

*《Effective Objective-C 2.0 編寫高質量iOS與OS X程式碼的52個有效方法》中的第4章 第 23條:通過委託與資料來源協議進行物件間通訊。

相關文章