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

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容

  • 转载来自网站:https://blog.csdn.net/qq_41739987/article/details/...
    梦婵阅读 72评论 0 0
  • 你为什么要来参加时间管理教练孵化? 你现在的时间管理困惑有哪些? 为什么是这些问题? 要求: 1.教练线至少参加两...
    旧话新听_efdf阅读 156评论 0 0
  • 1.自定义一个delegate 代理的作用:完成委托方交给的任务,委托方有一些任务自己不想完成,但是还需要要实现,...
    yangliangliang阅读 439评论 0 0
  • KVC赋值原理 调用方式 逻辑处理 总结 如果通过KVC给一个对象的的key赋值 首先会查找该对象是否有对应的se...
    hekyrun阅读 130评论 0 0
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,553评论 2 7