美文网首页
代理模式--delegate

代理模式--delegate

作者: sunrise_min | 来源:发表于2021-07-09 17:31 被阅读0次

    前言:

    最近回顾了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 <NSObject>
    
    @required
    
    - (void)studyPedagory;
    - (void)studyPsychology;
    
    @optional
    
    - (void)studyPainting;
    
    @end
    

    至此就生成了一个Pedagogy协议,里面声明了两个必选方法studyPedagorystudyPsychology,一个可选方法studyPainting

    • 2.创建一个需要遵守协议的类,

      我这里创建一个PedagoryStudent,一个继承于NSObject的类。

    // 1.在.h文件中遵守协议
    @interface PedagoryStudent : NSObject <Pedagogy>
    
    @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 <NSObject>
    
    - (void)didSelectButtonAction:(UIButton *)sender;
    
    @end
    
    @interface AView : UIView
    
    @property (nonatomic, weak) id <AViewDelegate> 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> // 遵守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 <NSObject>
    
    - (void)backWithMessage:(NSString *)message;
    
    @end
    
    @interface BViewController : UIViewController
    
    @property (nonatomic, weak) id <BViewControllerDelegate> 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 () <BViewControllerDelegate> // 遵守协议
    
    @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,并使用<xxDelegate>的语法方式遵循代理,实现代理方法;

    从上面实现可以看出来,有两个角色,

    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中添加一个节点,在运行时向遵守协议的对象发送消息即可。

    参考资料

    相关文章

      网友评论

          本文标题:代理模式--delegate

          本文链接:https://www.haomeiwen.com/subject/cxgfpltx.html