协议(Protocol) 和代理(Delegate)

1、概念与组成

delegate是iOS中一种常见的设计模式,是一种消息传递的的方式,常见的消息传递方式还有以下几种:

通知:在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式。
代理:是一种通用的设计模式,iOS中对代理支持的很好,由代理对象、委托者、协议三部分组成。
block:iOS4.0中引入的一种回调方法,可以将回调处理代码直接写在block代码块中,看起来逻辑清晰代码整齐。
target action:通过将对象传递到另一个类中,在另一个类中将该对象当做target的方式,来调用该对象方法,从内存角度来说和代理类似。
KVO:NSObject的Category-NSKeyValueObserving,通过属性监听的方式来监测某个值的变化,当值发生变化时调用KVO的回调方法。

我们可以通过一个简单的例子来解释什么是代理?什么是协议?

有个baby不会自己吃饭和洗澡等等做一些事情,于是baby就请了一个保姆,于是baby和保姆之间有了一个协议(Protocol)合同,协议合同中写明了保姆需要做什么事情, 而保姆就是要去完成这个协议中规定要做的事的代理人。
即:baby和保姆之间有个协议,保姆遵守该协议,于是保姆就需要实现该协议中的条款成为baby代理(delegate)人,而baby就是保姆的委托方。

说白了,代理的作用大家可以简单粗暴的理解为:"自己做不了的事情,就去雇佣一个可以做这些事的人,交给他去做!"

所以,我们从上面这个关系中可以看出,代理设计到了三个东西:委托方、代理方、协议,三者关系如下图所示↓↓↓

协议:用来指定代理双方可以做什么,必须做什么。
代理:根据指定的协议,完成委托方需要实现的功能。
委托:根据指定的协议,指定代理去完成什么功能。

2、协议

在实际应用中通过协议来规定代理双方的行为,协议中的内容一般都是方法列表,当代理方成为委托方代理并遵守相关协议后,委托方可以让代理方执行协议中规定的操作。

协议是公共的定义, 如果只是某个类使用,我们常做的就是写在某个类中。如果是多个类都是用同一个协议,建议创建一个Protocol文件,在这个文件中定义协议。
遵循的协议可以被继承,例如我们常用的UITableView,由于继承自UIScrollView的缘故,所以也将UIScrollViewDelegate继承了过来,我们可以通过代理方法获取UITableView偏移量等状态参数。 协议是由委托方来制定的,并写在委托方的文件中。代理方能做的就是成为代理、遵守协议、执行方法
某个类的协议创建方式↓
多个类的公共协议创建方式
  
 
协议只能定义公用的一套接口,类似于一个约束代理双方的作用。但不能提供具体的实现方法,实现方法需要代理对象去实现。比如说,房客起草了个合同,想要和房东签,这个合同中规定了当房客需要时房东要对房间设备进行维修。如果房客和房东签署合同后,那么这个合同就是协议,这里面只是对房东要做的事情做了规定,具体去做的还是房东,房客就是委托方,房东就是代理方。
协议可以继承其他协议,并且可以继承多个协议,在iOS中对象是不支持多继承的,而 协议可以多继承。
 

协议有两个修饰符@optional和@required,创建一个协议如果没有声明,默认是@required状态(必须实现)的。这两个修饰符只是约定代理是否强制需要遵守协议,如果@required状态的方法代理没有遵守,会报一个黄色的警告,只是起一个约束的作用,没有其他功能。【@required是需要我们必须实现的(不实现也只是报个黄色警告而已)。@optional是可以选择实现的.

无论是@optional还是@required,在委托方调用代理方法时都需要做一个判断,判断代理是否实现当前方法,否则会导致崩溃。

示例:// 判断代理对象是否实现这个方法,没有实现会导致崩溃
if([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) {
  [self.delegate userLoginWithUsername:self.username.text password:self.password.text];
}
扫描二维码关注公众号,回复: 5935076 查看本文章

3、实际演练

项目中有两个控制器,CDMyAddressListController(以下简称List)是用户地址列表控制器,CDAddAddressController(以下简称add)用户添加/删除地址的控制器,CDAddAddressController是从CDMyAddressListController push出来的

现在的需求是,当用户在CDAddAddressController删除地址后,要CDMyAddressListController中展现的是最新的数据:

这里可以用到的方法很多,比如说在删除地址成功后发送通知,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];
}

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

 4、实现原理

其实代理的实现没有涉及到什么底层的东西,只不过是代理对象内存的传递和操作。

我们在委托类设置代理对象后,实际上只是用一个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];
}

        

5、为什么用weak修饰delegate属性

为了避免循环引用

首先我们要知道我们在创建对象的时候如果没有特别说明的话我们默认的是strong强引用(在OC中,对象默认都是强指针,所以我们在list控制器中创建add对象后,list对象使其对add对象有一个强引用,

而add中的delegate其实就是引用的list对象,如果用strong来修饰的话,那么这两个引用都是强引用,就会出现循环引用的问题,导致双方都没办法去释放:

所以我们需要用weak来修饰delegate属性,这样add属性对list是一个弱引用,list对add是强引用,这样就避免了循环引用

参考资料

猜你喜欢

转载自www.cnblogs.com/gaoxiaoniu/p/10715144.html