iOS代理及延伸

前言: 在iOS中,delegate被叫做代理,故而由协议,委托方、代理三者组成的代理传值方式被口头称为代理传值、代理模式、代理等。这种A委托B的方式在设计模式里面被成为委托(设计)模式,但因为delegate被叫代理习惯了,又称代理模式。来探究一下这东西好玩的地方~

一、代理模式

1、先上一组demo,从实例谈起

协议、委托方

// 协议

#import <UIKit/UIKit.h>

@protocol FESTestProtocol <NSObject> // 名为FESTestProtocol的协议

@optional

- (void)testWithParameterOne:(NSString *_Nullable)one two:(NSString *_Nullable)two;

@end

NS_ASSUME_NONNULL_BEGIN

// 委托方
@interface FESClientViewController : UIViewController

@property (nonatomic, weak) id<FESTestProtocol> delegate; //!< 利用属性设置遵守FESTestProtocol协议的代理

@end

NS_ASSUME_NONNULL_END

简析:

  • 协议只有一个类使用的时,可以将该协议写在该类中;协议被多个地方使用时,多个类之间没有继承关系(其实有继承关系也罢)最好抽成一个单独的protocol文件,方便各处使用。
  • 利用属性设置遵守FESTestProtocol协议的代理,每一个遵守了FESTestProtocol协议的其他类,都可以被设置成为FESClientViewController的delegate属性,进而FESClientViewController就可以通过delegate操作该类,让其调用完成协议中的方法
@implementation FESClientViewController
/// 调用代理需要具体实现的协议方法
- (void)delegateAction {
    // 需要利用respondsToSelector去判断Agent(代理方)是否实现了协议中的方法,如果没有实现直接调用会引起crash
    if (self.delegate && [self.delegate respondsToSelector:@selector(testWithParameterOne:two:)]) {
        [self.delegate testWithParameterOne:@"one" two:@"two"];
    }
}
  • 在.m中去调用代理实现协议中的方法,要注意的是:需要利用respondsToSelector去判断Agent(代理方)是否实现了协议中的方法,如果没有实现直接调用会引起crash

代理方

@interface FESAgentViewController ()<FESTestProtocol>

@property (nonatomic, strong) FESClientViewController * clientViewController; //!< FESClientViewController类的对象
@end

@implementation FESAgentViewController
#pragma mark - FESTestProtocol
/// FESTestProtocol协议方法的具体实现
- (void)testWithParameterOne:(NSString *)one two:(NSString *)two {
    NSLog(@"这里收到委托方的调用,和传过来的参数,并对协议的方法做具体的实现操作%@-%@",one,two);
}

- (FESClientViewController *)clientViewController {
    if (!_clientViewController) {
        _clientViewController = [[FESClientViewController alloc]init];
        /*
         如果忘记遵守协议则会提示:
         Assigning to 'id<FESTestProtocol> _Nullable' from incompatible type 'FESAgentViewController *const __strong'
         */
        _clientViewController.delegate = self;
    }
    return _clientViewController;
}
  • 代理方要遵守协议: @interface FESAgentViewController ()< FESTestProtocol >
  • 要将代理方设置成委托方的delegate属性:_clientViewController.delegate = self;
  • 具体实现协议中的方法

简单的描述一下代理模式的使用步骤:
协议:

制定协议(该步骤可以放到代理方中,视情况来定)

委托方:

1、设置遵守协议的属性;
2、在合适的时候向代理方发送消息

代理方:

1、遵守协议(不遵守会有警告);
2、设置代理方作为委托方的delegate属性(不设置,委托方发送消息找不到);
3、完成代理方法中的具体实现

代理的实现方式就是这么几个步骤,看起来相对还是比较简单的

2、代理是按照什么逻辑跑起来的

其核心就在:消息机制。

先做个测试,

/// 调用代理需要具体实现的协议方法
- (void)delegateAction {
    // 需要利用respondsToSelector去判断Agent(代理方)是否实现了协议中的方法,如果没有实现直接调用会引起crash
//    if (self.delegate && [self.delegate respondsToSelector:@selector(testWithParameterOne:two:)]) {
//        [self.delegate testWithParameterOne:@"one" two:@"two"];
//    }
    [self.delegate testWithParameterOne:@"one" two:@"two"];
}
  • 第一步:委托方在发送消息的时候,不做校验去看代理方中是否有遵守的协议方法被实现;
#pragma mark - FESTestProtocol
/// FESTestProtocol协议方法的具体实现
//- (void)testWithParameterOne:(NSString *)one two:(NSString *)two {
//    NSLog(@"这里收到委托方的调用,和传过来的参数,并对协议的方法做具体的实现操作%@-%@",one,two);
//}
  • 第二步:代理方中不实现遵守的协议中的方法

在委托方向代理方发送消息时,发现程序crash,报错信息是经典的:

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[FESAgentViewController testWithParameterOne: two:]: unrecognized selector sent to instance 0x7f90b0602320’
*** First throw call stack:

那这个消息机制是怎么传递的呢 ?
在这里插入图片描述

  • 两个遵守分别代表:代理方遵守协议、委托方创建遵守协议的delegate属性
  • 步骤1代表:_clientViewController.delegate = self; 这一步操作,直接把代理方对象交给了委托方的delegate属性,其结果就是委托方id类型的delegate属性指针直接指向了代理方对象,使操作委托方的delegate属性就是在操作代理方对象
  • 步骤2中,不就是代理方在调用自己遵守的协议中的具体实现方法嘛~,那要是没有实现方法,可不就出现 unrecognized selector sent to instance reason的crash
3、简单描述协议、委托方、代理方的关系

上面已经分析了协议、委托方、代理方的实现步骤,再结合上面手绘图来简述一下。

  • 协议呢,就是龟腚委托方、代理方按协议标准去做,什么制定标准
  • 委托方呢,指定遵守了协议的代理方,去完成某个协议中的方法;
    委托方先是生成一个遵守协议的属性,然后再让成为该属性的代理对象去完成某个协议事件
  • 代理方呢,把遵守的协议中该完成的事件去具体实现;
    代理方要遵守协议(与委托方的属性遵守同一个协议),要成为委托方的属性,然后接受委托方的指示去完成事件

嘟嘟 ~ 往外延伸

二、协议、category

协议和分类可以单独拿出来做一篇,概念和细节还是有点内容的

三、为什么用weak修饰delegate?

平时修饰属性的时候,有一个宽泛的概念就是对于非基础类型的对象,一般使用strong来修饰,那id类型的delegate。为什么使用weak修饰而非strong呢?

其实就是strong的强引用的特性上。我们使用strong强引用的特性,也会避开strong强引用的特质。
在这里插入图片描述
首先梳理一下一整个代理的环节:

  • 制定好协议之后,委托方创建一个遵守协议的对象delegate(图中步骤2)
  • 代理方需要遵守协议,代理方需要拥有委托方对象(图中步骤1)
  • 需要将代理方设置成委托方的delegate(图中步骤3)

好,现在这是一个完整的闭环,在这个闭环中,步骤1和3都是强引用,在步骤2中,再加一个强引用的delegate,这个环势必会造成循环引用。所以我们使用weak去修饰,将这个闭环断开,组织循环引用的发生。

那现在上述问题解决了吗?没有!
发现没有问的是为什么要用weak,上述推理讲述的是为什么不用strong,在避开strong之后,为了使用若引用使用了weak,那弱引用还能使用assign,为什么不用呢!

@property (nonatomic, assign) id<FESTestProtocol> delegate; //!< 利用属性设置遵守FESTestProtocol协议的代理

来对比一个assign和weak:

assign: 不改变被修饰对象的引用计数,会产生悬垂指针
weak:不改变被修饰对象的引用计数,被修饰对象在释放之后,会被自动置为nil;

为了避免上述悬垂指针的问题,所以最好使用weak~

用案例说明:

代理方.m文件中

- (FESClientViewController *)clientViewController {
    if (!_clientViewController) {
        _clientViewController = [[FESClientViewController alloc]init];
//        _clientViewController.delegate = self;
    }
    return _clientViewController;
}

代理的设置这块做修改,并加入dealloc方法打印:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    FESAgentViewController *agent = [[FESAgentViewController alloc]init];
    self.clientViewController.delegate = agent;
    
}

-(void)dealloc{
    NSLog(@"---什么我被销毁了?---");
}

正常流程是,viewDidLoad方法执行完毕,agent被销毁,dealloc方法执行一次。

分别用不同的属性关键字去修饰委托方中的delegate:
1、strong

@property (nonatomic, strong) id<FESTestProtocol> delegate;
2019-12-01 14:21:42.216912+0800 Fe7sKnowledgeSummary-OC[8249:463308] 这里收到委托方的调用,和传过来的参数,并对协议的方法做具体的实现操作one-two
// 销毁FESAgentViewController页面时,连续执行了两次dealloc方法
2019-12-01 14:21:47.352813+0800 Fe7sKnowledgeSummary-OC[8249:463308] ---什么我被销毁了?---
2019-12-01 14:21:47.353198+0800 Fe7sKnowledgeSummary-OC[8249:463308] ---什么我被销毁了?---

使用strong之后,因为agent被强引用了,所以在viewDidLoad方法执行完毕之后不会销毁agent对象,也不会执行dealloc,会在销毁当前页面的时候才会销毁agent对象。

2、weak

@property (nonatomic, weak) id<FESTestProtocol> delegate;
// 在viewDidLoad方法执行完之后执行了dealloc方法
2019-12-01 14:24:22.589427+0800 Fe7sKnowledgeSummary-OC[8288:464939] ---什么我被销毁了?---
// 销毁FESAgentViewController页面时,再执行一次dealloc方法
2019-12-01 14:24:28.823152+0800 Fe7sKnowledgeSummary-OC[8288:464939] ---什么我被销毁了?---

因为weak弱引用,执行viewDidLoad方法之后,agent对象被销毁,所以打印了一次,然后agent对象没了,那self.clientViewController.delegate 是nil,所以调用已经被销毁的代理方的协议中方法的具体实现是不成功的,没有打印,代理方中协议方法实现中的log。

3、assign
在这里插入图片描述
assign弱引用,所以在agentViewController方法的viewDidLoad方法之执行完后,agent对象被销毁,所以打印了一次,然后agent对象没了,因为是assign修饰,self.clientViewController.delegate没有被置为nil,产生悬垂指针,当调用代理方的协议中方法的具体实现时,会出现EXC_BAD_ACCESS错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值