協議(Protocol) 和代理(Delegate)

一、概念與組成

delegate是iOS中一種常見的設計模式,是一種消息傳遞的的方式,常見的消息傳遞方式還有如下幾種:json

通知:在iOS中由通知中心進行消息接收和消息廣播,是一種一對多的消息傳遞方式。
代理:是一種通用的設計模式,iOS中對代理支持的很好,由代理對象、委託者、協議三部分組成。
block:iOS4.0中引入的一種回調方法,能夠將回調處理代碼直接寫在block代碼塊中,看起來邏輯清晰代碼整齊。
target action:經過將對象傳遞到另外一個類中,在另外一個類中將該對象當作target的方式,來調用該對象方法,從內存角度來講和代理相似。
KVO:NSObject的Category-NSKeyValueObserving,經過屬性監聽的方式來監測某個值的變化,當值發生變化時調用KVO的回調方法。

咱們能夠經過一個簡單的例子來解釋什麼是代理?什麼是協議?設計模式

有個baby不會本身吃飯和洗澡等等作一些事情,因而baby就請了一個保姆,因而baby和保姆之間有了一個協議(Protocol)合同,協議合同中寫明瞭保姆須要作什麼事情, 而保姆就是要去完成這個協議中規定要作的事的代理人。
即:baby和保姆之間有個協議,保姆遵照該協議,因而保姆就須要實現該協議中的條款成爲baby代理(delegate)人,而baby就是保姆的委託方。ui

說白了,代理的做用你們能夠簡單粗暴的理解爲:"本身作不了的事情,就去僱傭一個能夠作這些事的人,交給他去作!"atom

因此,咱們從上面這個關係中能夠看出,代理設計到了三個東西:委託方、代理方、協議,三者關係以下圖所示↓↓↓spa

協議:用來指定代理雙方能夠作什麼,必須作什麼。
代理:根據指定的協議,完成委託方須要實現的功能。
委託:根據指定的協議,指定代理去完成什麼功能。

 

二、協議

在實際應用中經過協議來規定代理雙方的行爲,協議中的內容通常都是方法列表,當代理方成爲委託方代理並遵照相關協議後,委託方可讓代理方執行協議中規定的操做。設計

協議是公共的定義, 若是隻是某個類使用,咱們常作的就是寫在某個類中。若是是多個類都是用同一個協議,建議建立一個Protocol文件,在這個文件中定義協議。
遵循的協議能夠被繼承,例如咱們經常使用的UITableView,因爲繼承自UIScrollView的緣故,因此也將UIScrollViewDelegate繼承了過來,咱們能夠經過代理方法獲取UITableView偏移量等狀態參數。 協議是由委託方來制定的,並寫在委託方的文件中。代理方能作的就是成爲代理、遵照協議、執行方法
某個類的協議建立方式↓
多個類的公共協議建立方式
  
 
協議只能定義公用的一套接口,相似於一個約束代理雙方的做用。但不能提供具體的實現方法,實現方法須要代理對象去實現。好比說,房客起草了個合同,想要和房東籤,這個合同中規定了當房客須要時房東要對房間設備進行維修。若是房客和房東簽署合同後,那麼這個合同就是協議,這裏面只是對房東要作的事情作了規定,具體去作的仍是房東,房客就是委託方,房東就是代理方。
協議能夠繼承其餘協議,而且能夠繼承多個協議,在iOS中對象是不支持多繼承的,而 協議能夠多繼承。
 

協議有兩個修飾符@optional和@required,建立一個協議若是沒有聲明,默認是@required狀態(必須實現)的。這兩個修飾符只是約定代理是否強制須要遵照協議,若是@required狀態的方法代理沒有遵照,會報一個黃色的警告,只是起一個約束的做用,沒有其餘功能。【@required是須要咱們必須實現的(不實現也只是報個黃色警告而已)。@optional是能夠選擇實現的.3d

不管是@optional仍是@required,在委託方調用代理方法時都須要作一個判斷,判斷代理是否實現當前方法,不然會致使崩潰。代理

示例:// 判斷代理對象是否實現這個方法,沒有實現會致使崩潰
if([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) {
  [self.delegate userLoginWithUsername:self.username.text password:self.password.text];
}

 

三、實際演練

項目中有兩個控制器,CDMyAddressListController(如下簡稱List)是用戶地址列表控制器,CDAddAddressController(如下簡稱add)用戶添加/刪除地址的控制器,CDAddAddressController是從CDMyAddressListController push出來的指針

如今的需求是,當用戶在CDAddAddressController刪除地址後,要CDMyAddressListController中展示的是最新的數據:code

這裏能夠用到的方法不少,好比說在刪除地址成功後發送通知,CDMyAddressListController接收到通知後作刷新處理,或者最笨的方法就是每次進入地址列表頁都進行刷新,固然,咱們也能夠經過代理來實現:

首先,分析誰是委託方,誰是代理方:

委託方經過協議來讓代理方作事情的,而在這個項目中,add控制器想要在特定時候讓List控制器去刷新,因此add控制器就是委託方,而List控制器就是代理方。

接下來,咱們來看協議:

由於這個協議只是在add這個類中會用到,因此寫在add類內部就能夠,不用再建立pertocol文件存放了;

這個協議只規定了一件事情,那就是刷新數據,並且當委託方發出需求是,代理方必需要實現,因此是@required狀態

//
//  CDAddAddressController.h
//  xx
//
//  Created by xx on 2019/4/11.
//  Copyright © 2019 xx. All rights reserved.
//

#import <UIKit/UIKit.h>
@class  CDMyAddressListModel;
//制定協議
@protocol CDAddAddressControllerDelegate <NSObject>
- (void)reloadDataToRefresh;
@end

@interface CDAddAddressController : UIViewController
//
使用協議修飾屬性,聲明當前屬性時已經遵照協議的,當代理方出現了該協議中的方法時,編譯器就不會報錯
//delegate屬性就是創建委託方和代理方的連接,→→ @property(nonatomic,weak) id delegate;
@property(nonatomic,weak) id<CDAddAddressControllerDelegate> delegate;
@property(nonatomic,strong) CDMyAddressListModel *model;
@end

制定完協議後,當委託方須要的時候就能夠指定代理方作事了:

CDAddAddressController.m


//刪除地址
- (void)delete{
    // 初始化對話框
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@"是否確認刪除改地址?" preferredStyle:UIAlertControllerStyleAlert];
    // 肯定註銷
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"肯定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) {
        [[CDWebService instance]deleteMyAddressWithParameters:@{@"address_id":self.model.address_id} success:^(id json) {
            if ([json[@"code"]integerValue] == 100) {
                [CDCommentAlertView showRequestStatus:statusNormal explain:@"刪除成功"];
                //要求代理方執行協議中的方法
                // 判斷代理方法是否存在
                if ([self.delegate respondsToSelector:@selector(reloadDataToRefresh)]) {
                    [self.delegate reloadDataToRefresh];
                }
                [self .navigationController popViewControllerAnimated:YES];
            }else{
                [CDCommentAlertView showRequestStatus:statusNormal explain:json[@"message"]];
            }
        } failure:^(NSError *error) {
               [CDUtils alertError:error];
        }];
        
    }];
    UIAlertAction *cancelAction =[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
    [alert addAction:okAction];
    [alert addAction:cancelAction];
    [self presentViewController:alert animated:true completion:nil]; 
}                        

而代理方如今須要作的就是成爲代理、遵照協議、實現代理方法

CDMyAddressListController.m
//在聲明部分遵照協議< CDAddAddressControllerDelegate >

//添加/刪除地址
- (void)addAddress{
    CDAddAddressController *add = [[CDAddAddressController alloc]init];
    add.delegate = self;
    [self.navigationController pushViewController:add animated: YES];
}


#warning CDAddAddressControllerDelegate
//經過代理來
- (void)reloadDataToRefresh{
    [self loadNewData];
}

至此,一個完整的代理就完成了。

 

 

 四、實現原理

其實代理的實現沒有涉及到什麼底層的東西,只不過是代理對象內存的傳遞和操做。

咱們在委託類設置代理對象後,實際上只是用一個id類型的指針將代理對象進行了一個弱引用委託方讓代理方執行操做,其實是在委託類中向這個id類型指針指向的對象發送消息,而這個id類型指針指向的對象,就是代理對象。

按上面例子來講的話,就是

CDAddAddressController *add = [[CDAddAddressController alloc]init];
//self就是list對象,self設置成add控制器的代理,實際上也就是成爲add控制器的一個屬性而已
add.delegate = self;
    
[self.navigationController pushViewController:add animated: YES];
//要求代理方執行協議中的方法
// 判斷代理方法是否存在
//delegate這個屬性自己存放的就是list控制器這個對象,檢測代理方法是否存在也就是至關與查看list對象中是否存在這個方法
if ([self.delegate respondsToSelector:@selector(reloadDataToRefresh)]) {
   //至關於 [list reloadDataToRefresh];  也就是list對象調用本身的方法      
    [self.delegate reloadDataToRefresh];
}

        

 

五、爲何用weak修飾delegate屬性

爲了不循環引用

首先咱們要知道咱們在建立對象的時候若是沒有特別說明的話咱們默認的是strong強引用(在OC中,對象默認都是強指針,因此咱們在list控制器中建立add對象後,list對象使其對add對象有一個強引用,

而add中的delegate其實就是引用的list對象,若是用strong來修飾的話,那麼這兩個引用都是強引用,就會出現循環引用的問題,致使雙方都沒辦法去釋放:

 

因此咱們須要用weak來修飾delegate屬性,這樣add屬性對list是一個弱引用,list對add是強引用,這樣就避免了循環引用

 

 

 

參考資料
相關文章
相關標籤/搜索