前言:
最近回顾了delegate,当初学习的时候就是简单的学习了delegate的使用规范,对于为什么这么写,脑海里模模糊糊。我参照了几篇博客,通过对她们讲解的理解,以及按照自己的理解,做了一个梳理。由于自身认知的有限,所以有错误的地方希望大家可以指正,对于引用博客也列在了文后,非常感谢分享。
梗概:
-
1.protocol
通过对于protocol的定义,使用,认知到自己心中理解的protocol的思想
-
2.项目中常见的delegate的场景
- UIView传递点击事件
- ViewController的回调传值
-
3.delegate的实现本质
- 指针变量的引用
-
4.delegate的内存管理
- 循环引用
- weak 和 assign的选择
-
5.delegate 与 block 使用选择
- 常用的使用场景和选择技巧
1. protocol
概述:
-
protocol就是一系列
约定
,只声明接口,不做实现。比如说按照不同职业发展将大学课程分成医学和教育学,分别约定了医学的课程学习和标准,以及教育学的课程和准备。这种根据不同的特征和需要进行的约定和iOS中的protocol就有异曲同工之妙。
-
遵守协议的对象
,可以自己完成协议中约定的实现;比如说:医学生学习医学,教育学学生学习教育学;并且大学课程是有选修和必修的,必修课是必须修的,选修课可以根据自己需求来进行学习。
Objective—C中,协议的创建、遵守协议的实现、协议的调用
1. 创建协议,声明协议方法
2. 遵守协议,实现协议方法
3. 协议方法的调用:调用的时候需要判断实现对象是否实现了协议方法,实现则调用。
- 1.创建协议文件
file-->new file -->选择Objective—C file-->选择protocol,我这里生成一个教育学的协议文件Pedagogy,点击完成就会生成一个Pedagogy.h的文件,在.h文件中添加协议方法,我这里添加了两个必选方法,和一个可选方法
@protocol Pedagogy
@required
- (void)studyPedagory;
- (void)studyPsychology;
@optional
- (void)studyPainting;
@end
至此就生成了一个Pedagogy协议,里面声明了两个必选方法studyPedagory
和studyPsychology
,一个可选方法studyPainting
-
2.创建一个需要遵守协议的类,
我这里创建一个
PedagoryStudent
,一个继承于NSObject的类。
// 1.在.h文件中遵守协议
@interface PedagoryStudent : NSObject
@end
// 2.在.m中添加协议方法自己的实现,其中`studyPedagory`、`studyPsychology`是必须实现的方法,`studyPainting`是可选实现方法,按照需求选择实现。
#import "IT.h"
@implementation IT
- (void)studyPedagory {
NSLog(@"PedagoryStudent study pedagory");
}
- (void)studyPsychology {
NSLog(@"PedagoryStudent study studyPsychology");
}
// 需要再实现
- (void)studyPainting {
NSLog(@"PedagoryStudent study Painting");
}
@end
- 3.协议方法的调用
现在协议已经生成,遵守协议的类也做了自己的实现。现在就来调用协议方法,我们在viewController中实现协议方法:主要注意一下,在调用的时候,判断一下对象是否实现了相应的方法,实现了再调用,避免找不到相应的实现,造成程序崩溃,报错unrecognized selector sent to instance 0x6000038b45e0"
。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self pedagoryStudentStudy];
// Do any additional setup after loading the view.
}
- (void)pedagoryStudentStudy {
PedagoryStudent *student = [[PedagoryStudent alloc] init];
if ([student respondsToSelector:@selector(studyPedagory)]) { // 需要判断协议方法是否实现,避免程序找不到对应实现,崩溃
[student studyPedagory];
}
if ([student respondsToSelector:@selector(studyPsychology)]) {
[student studyPsychology];
}
if ([student respondsToSelector:@selector(studyPainting)]) {
[student studyPainting];
}
}
@end
2.项目中常见delegate使用场景
项目实践中,比较常见应用场景有:
-
view的触摸方法传递给ViewController控制器来处理:
举一个简单的例子:AView是一个通用view,其中包含一个按钮,点击按钮的实现就需要不同的调用方自己处理,就很满足协议的需要将同样的行为抽取出来,让其他对象来自定义实现的设定。一般属于某一个类的协议,OC中是写在类中,如下:
// 1.AView的.h文件中声明AViewDelegate协议
@protocol AViewDelegate
- (void)didSelectButtonAction:(UIButton *)sender;
@end
@interface AView : UIView
@property (nonatomic, weak) id delegate; // 1. 声明遵守AViewDelegate的属性delegate,类型为id类型
@end
// 2. .m中添加一个button,完成简单的布局,并且在点击button的时候调用协议的实现
#import "AView.h"
@interface AView ()
@property (nonatomic, strong) UIButton *button;
@end
@implementation AView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self layoutUI];
}
return self;
}
- (void)layoutUI {
self.button.frame = CGRectMake(0, 0, 80, 80);
[self addSubview:_button];
}
- (UIButton *)button {
if (!_button) {
_button = [[UIButton alloc]init];
_button.titleLabel.textColor = [UIColor grayColor];
[_button setBackgroundColor:[UIColor systemPinkColor]];
[_button setTitle:@"点击" forState:UIControlStateNormal];
[_button addTarget:self action:@selector(touchButtonAction:) forControlEvents:UIControlEventTouchUpInside];
}
return _button;
}
- (void)touchButtonAction:(UIButton *)sender {
// 判断一下代理是否存在,并且协议方法是否实现,避免产生崩溃
if (self.delegate && [self.delegate respondsToSelector:@selector(didSelectButtonAction:)]) {
[self.delegate didSelectButtonAction:sender];
}
}
@end
// 3. 在ViewController中添加aview,并成为aview的代理
@interface ViewController () // 遵守AViewDelegate协议
@property (strong, nonatomic) AView *aview;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self layoutAView];
// Do any additional setup after loading the view.
}
- (AView *)aview {
if (!_aview) {
_aview = [[AView alloc] initWithFrame:CGRectMake(0, 100, 80, 80)];
_aview.delegate = self;// 设置ViewController为aview的代理
}
return _aview;
}
- (void)layoutAView {
[self.view addSubview:self.aview];
}
// 实现协议方法
- (void)didSelectButtonAction:(UIButton *)sender {
NSLog(@"ViewCOntroller didSelect Aview button");
}
@end
- 反向传值,下一个页面填写信息框,点击确定按钮之后,返回上一个页面,刷新显示刚刚填写的信息,通过代理回调填写的值给上一个页面。这里就不做具体的代码书写了,简单的写一下:AViewController -->BViewController,BViewController回调回来值:
// 1. BViewController的.h文件中声明协议和添加代理属性
@protocol BViewControllerDelegate
- (void)backWithMessage:(NSString *)message;
@end
@interface BViewController : UIViewController
@property (nonatomic, weak) id delegate;
@end
// 2. BViewController的.m文件中调用代理方法
@interface BViewController ()
@property (nonatomic, strong) UIButton *button;
@end
@implementation BViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.button.frame = CGRectMake(0, 0, 80, 80);
[self.view addSubView: self.button];
// Do any additional setup after loading the view.
}
- (UIButton *)button {
if (!_button) {
_button = [[UIButton alloc]init];
_button.titleLabel.textColor = [UIColor grayColor];
[_button setBackgroundColor:[UIColor systemPinkColor]];
[_button setTitle:@"点击" forState:UIControlStateNormal];
[_button addTarget:self action:@selector(touchButtonAction:) forControlEvents:UIControlEventTouchUpInside];
}
return _button;
}
- (void)touchButtonAction:(UIButton *)sender {
[self.navigationController popViewControllerAnimated:YES];
// 在点击button的时候调用
if (self.delegate && [self.delegate respondsToSelector:@selector(backWithMessage:)]) {
[self.delegate backWithMessage:@"message"];
}
}
@end
// 3. AViewController 中实现代理方法
@interface AViewController () // 遵守协议
@property (nonatomic, strong) UIButton *button;
@end
@implementation AViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.button.frame = CGRectMake(0, 0, 80, 80);
[self.view addSubView: self.button];
// Do any additional setup after loading the view.
}
- (UIButton *)button {
if (!_button) {
_button = [[UIButton alloc]init];
_button.titleLabel.textColor = [UIColor grayColor];
[_button setBackgroundColor:[UIColor systemPinkColor]];
[_button setTitle:@"点击" forState:UIControlStateNormal];
[_button addTarget:self action:@selector(touchButtonAction:) forControlEvents:UIControlEventTouchUpInside];
}
return _button;
}
- (void)touchButtonAction:(UIButton *)sender {
BViewController *vc = [[BViewController alloc] init];
vc.delegate = self; // 设置代理
[self.navigationController pushViewController:vc animated:YES];
}
// 实现代理方法
- (void)backWithMessage:(NSString *)message {
NSLog(@"BViewController message is %@", message);
}
@end
3. delegate的实现本质
在搜索很多博客的时候我发现我越看,越觉得和我理解的delegate不一样,直到我看到iOS代理设计模式,才和我的想法契合,所以这里也记一下笔记。
综上,无论是view传递点击事件、ViewController的传递事件,都是“我”有的事情需要别人去处理,使用协议来实现的。比如说AView中的按钮被点击之后,调用AView的对象需要做什么样子的处理是由对象决定的,没法在AView中直接实现,是因对象而异的;比如说BViewController中的在返回的时候获取了一个message信息,需要传递出去,给别的对象使用的;
我们来整理一下实现delegate的流程:
在需要的类中:(比如上方的AView、BViewController中),声明协议,包括其中的协议方法;声明遵守协议的id类型的属性,一般命名为
xxDelegate
;调用协议方法(别忘记判断代理是否存在,以及协议方法是否实现)在代理类中:(比如上方的ViewController、AViewController中),将
xxDelegate
引用self,并使用的语法方式遵循代理,实现代理方法;
从上面实现可以看出来,有两个角色,
1. 一个是像AView、BViewController,称为`委托类`:实现了协议的声明、代理属性的声明、协议方法的调用;
2. 一个是像ViewController、AViewController,称为`代理类`: 遵守协议、实现协议方法
代理本质就是委托类
需要别人帮助完成实现,通过协议定义一系列约定方法,再由代理类
遵守协议,并实现协议方法的一个模式。其中这个委托代理的实现,是通过委托类
声明一个id类型的属性变量,在使用的时候,将真正实现的代理对象赋值给指针变量,也就是xxobj.delegate = self
的实现。这个是我的理解,下面是引用iOS代理设计模式的总结,讲的很好,我直接贴上了。
代理的实现
- 在iOS中代理的本质就是代理对象内存的传递和操作,我们在委托类设置代理对象后,实际上只是用一个id类型的指针将代理对象进行了一个弱引用。委托方让代理方执行操作,实际上是在委托类中向这个id类型指针指向的对象发送消息,而这个id类型指针指向的对象,就是代理对象。
通过上面这张图我们发现,其实委托方的代理属性本质上就是代理对象自身,设置委托代理就是代理属性指针指向代理对象,相当于代理对象只是在委托方中调用自己的方法,如果方法没有实现就会导致崩溃。从崩溃的信息上来看,就可以看出来是代理方没有实现协议中的方法导致的崩溃。
而协议只是一种语法,是声明委托方中的代理属性可以调用协议中声明的方法,而协议中方法的实现还是由代理方完成,而协议方和委托方都不知道代理方有没有完成,也不需要知道怎么完成。
4. 代理的内存管理
至于委托类中的代理属性使用weak
修改,是为了避免循环引用。本身代理对象强引用委托对象,委托对象的代理属性如果设置为strong
,也就对代理对象强引用。A-->B,B-->A,形成了一个环,内存无法释放,也就称为循环引用。
而使用weak
为什么就可以解决循环引用呢?因为weak
不会导致引用对象的引用计数+1,是一种弱引用,从而打破了上面说的环。这里需要了解一些关于iOS内存管理,引用计数相关的知识。
weak和assign是一种“非拥有关系”的指针,通过这两种修饰符修饰的指针变量,都不会改变被引用对象的引用计数。但是在一个对象被释放后,weak会自动将指针指向nil,而assign则不会。在iOS中,向nil发送消息时不会导致崩溃的,所以assign就会导致野指针的错误unrecognized selector sent to instance。
5. delegate 与 block的使用选择
这里我是简单的记录下博主的整理,等以后自己有新的思考的时候再来补充
1.协议方法比较多的时候使用delegate,代码后期比价好维护,并且条理比较清晰,比如UITableView;
2.一个委托对象的代理属性只能有一个代理对象,如果想要委托对象调用多个代理对象的回调应该用block。
代理更加面相过程,block则更面向结果。从设计模式的角度来说,代理更佳面向过程,而block更佳面向结果。例如我们使用NSXMLParserDelegate代理进行XML解析,NSXMLParserDelegate中有很多代理方法,NSXMLParser会不间断调用这些方法将一些转换的参数传递出来,这就是NSXMLParser解析流程,这些通过代理来展现比较合适。而例如一个网络请求回来,就通过success、failure代码块来展示就比较好。
从性能上来说,block的性能消耗要略大于delegate,因为block会涉及到栈区向堆区拷贝等操作,时间和空间上的消耗都大于代理。而代理只是定义了一个方法列表,在遵守协议对象的
objc_protocol_list
中添加一个节点,在运行时向遵守协议的对象发送消息即可。
参考资料
- iOS代理设计模式