代理模式--delegate

前言:

最近回顾了delegate,当初学习的时候就是简单的学习了delegate的使用规范,对于为什么这么写,脑海里模模糊糊。我参照了几篇博客,通过对她们讲解的理解,以及按照自己的理解,做了一个梳理。由于自身认知的有限,所以有错误的地方希望大家可以指正,对于引用博客也列在了文后,非常感谢分享。

梗概:

  • 1.protocol

    通过对于protocol的定义,使用,认知到自己心中理解的protocol的思想

  • 2.项目中常见的delegate的场景

    • UIView传递点击事件
    • ViewController的回调传值
  • 3.delegate的实现本质

    • 指针变量的引用
  • 4.delegate的内存管理

    • 循环引用
    • weak 和 assign的选择
  • 5.delegate 与 block 使用选择

    • 常用的使用场景和选择技巧

1. protocol

概述:

  1. protocol就是一系列约定,只声明接口,不做实现。

    比如说按照不同职业发展将大学课程分成医学和教育学,分别约定了医学的课程学习和标准,以及教育学的课程和准备。这种根据不同的特征和需要进行的约定和iOS中的protocol就有异曲同工之妙。

  2. 遵守协议的对象,可以自己完成协议中约定的实现;

    比如说:医学生学习医学,教育学学生学习教育学;并且大学课程是有选修和必修的,必修课是必须修的,选修课可以根据自己需求来进行学习。

  3. 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协议,里面声明了两个必选方法studyPedagorystudyPsychology,一个可选方法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使用场景

项目实践中,比较常见应用场景有:

  1. 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

  1. 反向传值,下一个页面填写信息框,点击确定按钮之后,返回上一个页面,刷新显示刚刚填写的信息,通过代理回调填写的值给上一个页面。这里就不做具体的代码书写了,简单的写一下: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类型指针指向的对象,就是代理对象。
image.png

通过上面这张图我们发现,其实委托方的代理属性本质上就是代理对象自身,设置委托代理就是代理属性指针指向代理对象,相当于代理对象只是在委托方中调用自己的方法,如果方法没有实现就会导致崩溃。从崩溃的信息上来看,就可以看出来是代理方没有实现协议中的方法导致的崩溃。

而协议只是一种语法,是声明委托方中的代理属性可以调用协议中声明的方法,而协议中方法的实现还是由代理方完成,而协议方和委托方都不知道代理方有没有完成,也不需要知道怎么完成。

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代理设计模式

你可能感兴趣的:(代理模式--delegate)