1、概念与组成

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

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

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

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

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

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

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

2、协议

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

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

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

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

  1. 示例:// 判断代理对象是否实现这个方法,没有实现会导致崩溃
  2. if([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) {
  3.   [self.delegate userLoginWithUsername:self.username.text password:self.password.text];
  4. }

3、实际演练

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

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

这里可以用到的方法很多,比如说在删除地址成功后发送通知,CDMyAddressListController接收到通知后做刷新处理,或者最笨的方法就是每次进入地址列表页都进行刷新,当然,我们也可以通过代理来实现:

首先,分析谁是委托方,谁是代理方:

委托方通过协议来让代理方做事情的,而在这个项目中,add控制器想要在特定时候让List控制器去刷新,所以add控制器就是委托方,而List控制器就是代理方。

接下来,我们来看协议:

因为这个协议只是在add这个类中会用到,所以写在add类内部就可以,不用再创建pertocol文件存放了;

这个协议只规定了一件事情,那就是刷新数据,而且当委托方发出需求是,代理方必须要实现,所以是@required状态

  1. //
  2. // CDAddAddressController.h
  3. // xx
  4. //
  5. // Created by xx on 2019/4/11.
  6. // Copyright © 2019 xx. All rights reserved.
  7. //
  8.  
  9. #import <UIKit/UIKit.h>
  10. @class CDMyAddressListModel;
  11. //制定协议
  12. @protocol CDAddAddressControllerDelegate <NSObject>
  13. - (void)reloadDataToRefresh;
  14. @end
  15.  
  16. @interface CDAddAddressController : UIViewController
  17. //使用协议修饰属性,声明当前属性时已经遵守协议的,当代理方出现了该协议中的方法时,编译器就不会报错
    //delegate属性就是建立委托方和代理方的链接,→→ @property(nonatomic,weak) id delegate;
  1. @property(nonatomic,weak) id<CDAddAddressControllerDelegate> delegate;
  1. @property(nonatomic,strong) CDMyAddressListModel *model;
  2. @end

制定完协议后,当委托方需要的时候就可以指定代理方做事了:

  1. CDAddAddressController.m
  2.  
  3. //删除地址
  4. - (void)delete{
  5. // 初始化对话框
  6. UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@"是否确认删除改地址?" preferredStyle:UIAlertControllerStyleAlert];
  7. // 确定注销
  8. UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) {
  9. [[CDWebService instance]deleteMyAddressWithParameters:@{@"address_id":self.model.address_id} success:^(id json) {
  10. if ([json[@"code"]integerValue] == ) {
  11. [CDCommentAlertView showRequestStatus:statusNormal explain:@"删除成功"];
  12. //要求代理方执行协议中的方法
  13. // 判断代理方法是否存在
  14. if ([self.delegate respondsToSelector:@selector(reloadDataToRefresh)]) {
  15. [self.delegate reloadDataToRefresh];
  16. }
  17. [self .navigationController popViewControllerAnimated:YES];
  18. }else{
  19. [CDCommentAlertView showRequestStatus:statusNormal explain:json[@"message"]];
  20. }
  21. } failure:^(NSError *error) {
  22. [CDUtils alertError:error];
  23. }];
  24.  
  25. }];
  26. UIAlertAction *cancelAction =[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
  27. [alert addAction:okAction];
  28. [alert addAction:cancelAction];
  29. [self presentViewController:alert animated:true completion:nil];
  30. }

而代理方现在需要做的就是成为代理、遵守协议、实现代理方法

  1. CDMyAddressListController.m
  2. //在声明部分遵守协议< CDAddAddressControllerDelegate >
  3.  
  4. //添加/删除地址
  5. - (void)addAddress{
  6. CDAddAddressController *add = [[CDAddAddressController alloc]init];
  7. add.delegate = self;
  8. [self.navigationController pushViewController:add animated: YES];
  9. }
  10.  
  11. #warning CDAddAddressControllerDelegate
  12. //通过代理来
  13. - (void)reloadDataToRefresh{
  14. [self loadNewData];
  15. }

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

4、实现原理

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

我们在委托类设置代理对象后,实际上只是用一个id类型的指针将代理对象进行了一个弱引用。委托方让代理方执行操作,实际上是在委托类中向这个id类型指针指向的对象发送消息,而这个id类型指针指向的对象,就是代理对象。

按上面例子来说的话,就是

  1. CDAddAddressController *add = [[CDAddAddressController alloc]init];
  2. //self就是list对象,self设置成add控制器的代理,实际上也就是成为add控制器的一个属性而已
  3. add.delegate = self;
  4.  
  5. [self.navigationController pushViewController:add animated: YES];
  1. //要求代理方执行协议中的方法
  2. // 判断代理方法是否存在
  3. //delegate这个属性本身存放的就是list控制器这个对象,检测代理方法是否存在也就是相当与查看list对象中是否存在这个方法
  4. if ([self.delegate respondsToSelector:@selector(reloadDataToRefresh)]) {
  5. //相当于 [list reloadDataToRefresh]; 也就是list对象调用自己的方法
  6. [self.delegate reloadDataToRefresh];
  7. }

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

为了避免循环引用

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

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

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

参考资料

协议(Protocol) 和代理(Delegate)的更多相关文章

  1. 浅谈iOS开发的协议(protocol)和代理(delegate)

    协议和代理对于一个新手来说确实不讨好理解,也有很多的iOS开发的老手对此是懂非懂的.网上的很多博文只是讲了怎么使用,并没有说的很明白.下面我谈一下我的理解. 1.你要先搞明白,协议和代理为什么会出现, ...

  2. 窥探Swift之协议(Protocol)和委托代理(Delegate)回调的使用

    协议与委托代理回调在之前的博客中也是经常提到和用到的在<Objective-C中的委托(代理)模式>和<iOS开发之窥探UICollectionViewController(四) - ...

  3. 协议(Protocol)与委托代理(Delegate)

    协议(Protocol)的作用: 1. 规范接口,用来定义一套公用的接口: 2. 约束或筛选对象. 代理(Delegate): 它本身是一种设计模式,委托一个对象<遵守协议>去做某件事情, ...

  4. 【转】iOS开发-Protocol协议及委托代理(Delegate)传值

    原文网址:http://www.cnblogs.com/GarveyCalvin/p/4210828.html 前言:因为Object-C是不支持多继承的,所以很多时候都是用Protocol(协议)来 ...

  5. iOS开发-Protocol协议及委托代理(Delegate)传值

    前言:因为Object-C是不支持多继承的,所以很多时候都是用Protocol(协议)来代替.Protocol(协议)只能定义公用的一套接口,但不能提供具体的实现方法.也就是说,它只告诉你要做什么,但 ...

  6. 协议Protocol

    1.协议:是一组声明方法的集合,不能声明成员变量,作用类似于接口.           遵守此协议的类就相当于拥有了这个协议的所有方法的声明,如果父类遵守了某个协议,子类也遵守了这个协议.       ...

  7. iOS 的一点理解(一) 代理delegate

    做了一年的iOS,想记录自己对知识点的一点理解. 第一篇,想记录一下iOS中delegate(委托,也有人称作代理)的理解吧. 故名思议,delegate就是代理的含义, 一件事情自己不方便做,然后交 ...

  8. swift -- 代理delegate

    1.声明协议 protocol SecondDelagate { func sendValue(text : String!) -> Void } 2.声明代理属性 var delegate : ...

  9. 代理delegate

    1>代理的用处是什么? 监听那些不能通过addTarget监听的事件 主要用开负责在两个对象之间,发生某些事件时,传递或发送消息 当我们需要 监听某些事件时,但苹果没有提供相关监听方法(addt ...

随机推荐

  1. VS2017git 提交提示错误 Git failed with a fatal error.

    具体错误信息:Git failed with a fatal error.error: open("ConsoleApp1/.vs/ConsoleApp1/v15/Server/sqlite ...

  2. 费用最少的一款赛门铁克SSL证书

    Symantec Secure Site SSL证书,验证域名所有权和企业信息,属于Symantec Class 3企业(OV)验证 级SSL证书,为40位/56位/128/256位自适应加密,目前连 ...

  3. 【Codeforces 467C】George and Job

    [链接] 我是链接,点我呀:) [题意] 让你从1..n这n个数字中 选出来k个不相交的长度为m的区间 然后这个k个区间的和最大 求出这k个区间的和的最大值 [题解] 设dp[i][j]表示前i个数字 ...

  4. Dubbo学习总结(2)——Dubbo架构详解

    一.前言 部门去年年中开始各种改造,第一步是模块服务化,这边初选dubbo试用在一些非重要模块上,慢慢引入到一些稍微重要的功能上,半年时间,学习过程及线上使用遇到的些问题在此总结下. 整理这篇文章差不 ...

  5. CF410div2 B. Mike and strings

    /* CF410div2 B. Mike and strings http://codeforces.com/contest/798/problem/B 字符串 暴力 题意:给你n个串,每次操作可以将 ...

  6. [转]WCF的几种寄宿方式

    转自:WCF开发框架形成之旅---WCF的几种寄宿方式 WCF寄宿方式是一种非常灵活的操作,可以在IIS服务.Windows服务.Winform程序.控制台程序中进行寄宿,从而实现WCF服务的运行,为 ...

  7. poj 1837 Balance (0 1 背包)

    Balance Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 10326   Accepted: 6393 题意:给你n个挂 ...

  8. Implementing Software Timers - Don Libes

         在看APUE习题10.5的时候提示了这篇文章,讲的非常清晰,设计也非常巧妙,所以把原文放在这里.值得自己去实现. Title: Implementing Software Timers By ...

  9. java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

    1.错误描写叙述 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String service. ...

  10. TF101出现“DMClient已停止”处理办法

    设定->应用程式->全部->DMClient强制停止 然后 清除数据 然后 重开机 测试通过.