Delegate介绍

代理介绍

代理(Delegate)是一种软件设计模式
传递方式一对一

代理分三个模块:协议、委托、代理
协议,就是@protocol
委托,拥有协议,且调用协议里面的方法
代理,遵守协议,将协议的代理设置成自己,且真正实现协议里面方法

按角色划分:
协议 = 合同
委托方 = 老板
代理方 = 员工

在这里插入图片描述

代理的用法:

协议:

@protocol testDelegate <NSObject>
@optional
- (void)delegateAction;
@end

委托方:

拥有代理,代理作为委托方的一个属性,weak

@property (nonatomic, weak) id<testDelegate> delegate;
#pragma mark - action
- (void)buttonClicked:(UIButton *)btn{
    if([self.delegate respondsToSelector:@selector(delegateAction)]){
    	//代理的调用
        [self.delegate delegateAction];
    }
}

这里需要注意,需要先判断一下代理的实例是否存在,并且指向的委托方是否能够响应事件,如果委托方没办法响应事件,而代理方发送了结果时,会因为找不到方法的原因而引起崩溃。
unrecognized selector sent to instance 0x7f887d806ef0'

代理方:

顶部遵守协议:<testDelegate>

//设置委托方的代理为自己
委托方.delegate = self;

//代理方法的实现
- (void)delegateAction
{
    NSLog(@"111111");
}

iOS 协议 委托 代理 delegate

代理的原理

代理的本质,其实是:代理方内存地址的传递

我们在代理方里面写这一行代码:
委托方.delegate = self;
self的内存地址,赋值给委托方.delegate
委托方.delegate的内存地址也是self代理方的内存地址

然后,在委托方执行代理方法:

    if([self.delegate respondsToSelector:@selector(delegateAction)]){
        [self.delegate delegateAction];
    }

此时,self.delegate=委托方.delegate=代理方,即代理方的地址
也就是,执行[代理方 delegateAction]

iOS开发-消息传递方式-代理(delegate)及协议(protocol)


针对代理,有几个问题:

1. 问:代理为何使用weak修饰?

在这里插入图片描述
主要是:委托方.delegate = self这句话导致的


2. 问:如何实现代理的一对多?

如何理解一对多?
是:委托方执行一个代理方法,代理方执行对个方法?
比如,控制器B调用代理方法,使得控制器A上面三个图片分别赋值
这样的话,完全可以一个方法完成,做判断即可
或者,执行三个代理方法,控制器A上实现三个方法即可

还是:委托方执行一个代理方法,多个代理方响应该方法?

实现代理一对多,是说:同一个代理,被多个对象监听

方法一:

将代理设置成数组、集合,而不是单单的一个weak属性值

定义 CustomDelegate 协议

首先定义一个协议,声明需要多个对象响应的方法。

@protocol CustomDelegate <NSObject>
@optional
- (void)doSomething;
@end
创建一个管理多个 delegate 的类

然后,创建一个类来管理多个 delegate。

// DelegatesManager.h

#import <Foundation/Foundation.h>
#import "CustomDelegate.h"

@interface DelegatesManager : NSObject

- (void)addDelegate:(id<CustomDelegate>)delegate;
- (void)removeDelegate:(id<CustomDelegate>)delegate;
- (void)notifyDelegates;

@end
// DelegatesManager.m

#import "DelegatesManager.h"

@interface DelegatesManager ()
@property (nonatomic, strong) NSHashTable<id<CustomDelegate>> *delegates;
@end

@implementation DelegatesManager
+ (id)sharedInstance
{
    // 静态局部变量
    static DelegatesManager *instance = nil;
    
    // 通过dispatch_once方式 确保instance在多线程环境下只被创建一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 创建实例
        instance = [[super allocWithZone:NULL] init];
    });
    return instance;
}

// 重写方法【必不可少】
+ (id)allocWithZone:(struct _NSZone *)zone{
    return [self sharedInstance];
}

// 重写方法【必不可少】
- (id)copyWithZone:(nullable NSZone *)zone{
    return self;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _delegates = [NSHashTable weakObjectsHashTable];
    }
    return self;
}

- (void)addDelegate:(id<CustomDelegate>)delegate {
    [self.delegates addObject:delegate];
}

- (void)removeDelegate:(id<CustomDelegate>)delegate {
    [self.delegates removeObject:delegate];
}

- (void)notifyDelegates {
    for (id<CustomDelegate> delegate in self.delegates) {
        if ([delegate respondsToSelector:@selector(doSomething)]) {
            [delegate doSomething];
        }
    }
}

@end
使用 DelegatesManager

在每个代理对象中,实现:

//引入manager
#import "DelegatesManager.h"

遵守协议<CustomDelegate>

//设置代理
//确保只有一份
DelegatesManager *manager = [DelegatesManager sharedInstance];
//添加到数组里面
[manager addDelegate:self];

//实现代理方法
- (void)doSomething
{
    NSLog(@"222222%@", self);
}

- (void)dealloc
{
	//尽管使用了 `NSHashTable` 的弱引用,但良好的内存管理习惯仍然重要。
    DelegatesManager *manager = [DelegatesManager sharedInstance];
    //从代理中的集合里面,移除自己
    [manager removeDelegate:self];
}

在委托方:

DelegatesManager *manager = [DelegatesManager sharedInstance];
//协议方法执行
[manager notifyDelegates];

使用 NSHashTable 来存储多个 delegate 对象。NSHashTable 类似于 NSSet,但可以存储弱引用(weak references)的对象,这意味着当 delegate 对象被释放时,它们会自动从表中移除,这有助于防止悬挂指针和内存泄漏。

该方法有没有循环引用?

委托可以正常释放
代理1,虽然被加入到集合,但是弱引用集合,因此不会对代理1进行额外的引用,因此,可以正常释放
同理,代理2
而manager由于是单例,则不会被释放
在这里插入图片描述

可以使用NSMutableArray装作为代理集合
但是,必须在委托方的dealloc方法中,移除所有代理者

DelegatesManager.h文件中:
- (void)removeAllDelegate;

DelegatesManager.m文件中:
- (void)removeAllDelegate
{
    [self.delegates removeAllObjects];
}

在委托方的dealloc方法中:
- (void)dealloc
{
    DelegatesManager *manager = [DelegatesManager sharedInstance];
    [manager removeAllDelegate];
}

上面的代码就必须写,不然,代理方不会被释放

iOS 实现一对多代理方案

不过,最好使用NSMapTable NSHashTable NSPointerArray这些数据类型
因为,这几个数据类型,里面存放的元素都是weak指针引用的,销毁的时候不需要手动销毁

NSMutableArray里面,加一个对象,NSMutableArray就会对对象做一个引用计数器+1的操作
NSHashTable等集合,不会对集合里面的对象做+1操作

验证:

@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *dataSourceArray;
@property (nonatomic, strong) NSHashTable *weakHashTable;

@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建一个弱引用对象的 hash table
    self.weakHashTable = [NSHashTable weakObjectsHashTable];
    
    NSObject *p10 = [[NSObject alloc] init];
    NSObject *p11 = [[NSObject alloc] init];
    
    [self.weakHashTable addObject:p10];
    [self.weakHashTable addObject:p11];
    
    //创建一个NSMutableArray
    self.dataSourceArray = [NSMutableArray array];
    
    NSObject *p20 = [[NSObject alloc] init];
    NSObject *p21 = [[NSObject alloc] init];
    
    [self.dataSourceArray addObject:p20];
    [self.dataSourceArray addObject:p21];
}

//在其他地方进行打印
NSLog(@"%@", self.weakHashTable);
NSLog(@"%@", self.dataSourceArray);
打印结果:
NSHashTable {
}
(
    "<NSObject: 0x600000aac050>",
    "<NSObject: 0x600000aac210>"
)  

从结果可以看出,viewDidLoad大括号运行完毕:
由于weakHashTable不能强引用里面的对象,因此,NSHashTable里面的数据为空
由于NSMutableArray可以强引用里面的对象,因此,NSMutableArray里面的数据不为空

NSMapTable 对应 NSMutableDictionary
NSHashTable 对应 NSMutableSet
NSPointerArray 对应 NSMutableArray

iOS开发-NSMapTable NSHashTable NSPointerArray的使用

方法二:

利用消息转发机制

  1. 创建一个 DelegateManager(管理器)类,用来作为代理分发的中心实例。此实例当 delegate 被调用时,遍历消息并分发给所有注册的对象。

DelegateManager.h文件:

#import <Foundation/Foundation.h>

@interface DelegateManager : NSProxy
- (void)addDelegate:(id)delegate;
- (void)removeDelegate:(id)delegate;
@end

DelegateManager.m文件:

#import "DelegateManager.h"

@interface DelegateManager ()
//弱引用集合
@property (nonatomic, strong) NSPointerArray *delegates;
@end

@implementation DelegateManager

- (instancetype)init {
    _delegates = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory];
    return self;
}

- (void)addDelegate:(id)delegate {
    [self.delegates addPointer:(void *)delegate];
}

- (void)removeDelegate:(id)delegate {
    NSUInteger index = [self.delegates.allObjects indexOfObject:delegate];
    if (index != NSNotFound) {
        [self.delegates removePointerAtIndex:index];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    NSMethodSignature *signature;
    for (id delegate in self.delegates.allObjects) {
    	//首先判断[delegate methodSignatureForSelector:selector],有值赋值给signature,然后执行if里面的内容。类似init初始化
    	//[代理对象 methodSignatureForSelector:selector],这个是找的到的
        if ((signature = [delegate methodSignatureForSelector:selector])) {
            return signature;
        }
    }
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    for (id delegate in self.delegates.allObjects) {
    	//数组里面的元素就是代理对象,且每个对象都会执行invocation.selector,其实就是协议方法
        if ([delegate respondsToSelector:invocation.selector]) {
        	//执行,执行对象是delegate,即代理对象
            [invocation invokeWithTarget:delegate];
        }
    }
}

@end

然后是调用:

一个委托者(例如,一个事件发布者)和多个代理(例如,事件订阅者)的交互。
假设有一个简单的事件协议,以及如何在事件发生时通知所有注册的代理。

步骤 1: 定义事件协议

首先,定义一个事件协议EventDelegate,该协议包含委托者将调用的方法。

// EventDelegate.h

#import <Foundation/Foundation.h>

@protocol EventDelegate <NSObject>
@optional
- (void)eventDidHappenWithData:(NSDictionary *)data;
@end

这个协议声明了一个方法eventDidHappenWithData:,任何想要监听事件的代理都应该实现这个方法。

步骤 2: 委托者的实现

然后,创建一个EventPublisher类,这个类负责事件的发生,并通知所有的代理。

// EventPublisher.h

#import <Foundation/Foundation.h>
#import "DelegateManager.h"
#import "EventDelegate.h"

@interface EventPublisher : NSObject
@property (nonatomic, strong) DelegateManager *delegateManager;
- (void)triggerEvent;
@end

// EventPublisher.m

#import "EventPublisher.h"

@implementation EventPublisher

- (instancetype)init {
    if (self = [super init]) {
        _delegateManager = [[DelegateManager alloc] init];
    }
    return self;
}

- (void)triggerEvent {
    // 模拟事件数据
    NSDictionary *eventData = @{@"key": @"value"};
    
    // 通知所有代理事件发生了
    if ([self.delegateManager respondsToSelector:@selector(eventDidHappenWithData:)]) {
        [(id<EventDelegate>)self.delegateManager eventDidHappenWithData:eventData];
    }
}

@end
步骤 3: 多个代理的实现

创建两个简单的代理类,它们都遵循EventDelegate协议。

// EventListener1.h

#import <Foundation/Foundation.h>
#import "EventDelegate.h"

@interface EventListener1 : NSObject <EventDelegate>
@end

// EventListener1.m

#import "EventListener1.h"

@implementation EventListener1

- (void)eventDidHappenWithData:(NSDictionary *)data {
    NSLog(@"EventListener1 received event with data: %@", data);
}

@end

// 为 EventListener2 重复上述过程...
步骤 4: 使用示例

最后,展示如何将所有这些组件连接起来,触发事件并通知所有的代理。

#import "EventPublisher.h"
#import "EventListener1.h"
#import "EventListener2.h" // 假设你已经创建了 EventListener2

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        EventPublisher *publisher = [[EventPublisher alloc] init];
        
        // 创建监听者实例
        EventListener1 *listener1 = [[EventListener1 alloc] init];
        EventListener2 *listener2 = [[EventListener2 alloc] init]; // 假设这个类的实现类似于 EventListener1
        
        // 注册监听者
        [publisher.delegateManager addDelegate:listener1];
        [publisher.delegateManager addDelegate:listener2];
        
        // 触发事件
        [publisher triggerEvent];
    }
    return 0;
}

上述代码展示了:如何初始化事件发布者(EventPublisher),创建并注册两个事件监听者(EventListener1EventListener2),以及如何触发一个事件并通过DelegateManager将其通知给所有注册的代理。每个监听器在接收到事件时都会打印一条消息,显示它已经收到了包含特定数据的事件通知。

以上就是如何使用runtime来实现delegate一对多模式的示例。这样每次 delegate 调用方法的时候,会被 DelegateManager 截获并转发给所有注册的对象。

核心点:

最主要的就是,调用[publisher triggerEvent];方法,里面会执行
self.delegateManager eventDidHappenWithData:eventData,报方法找不到,因此会走消息发送机制,又因为manager是继承NSProxy,因此,直接走方法签名
然后在方法签名里面,for循环,取出数组元素,调用元素对应的协议方法
相当于数组里面的对象,都执行了一遍协议,也就完成了代理一对多

iOS:利用消息转发机制实现多播委托


3. 问:可以直接为代理添加属性吗?如果不可以,有没有间接办法为代理添加属性?

在 Objective-C 中,delegate 层面并不直接支持添加属性。这是因为 delegate 通常被定义为遵循某个协议的对象,而协议自身只用于声明方法和属性的接口,并不实现存储属性。但是,有几种间接的方法可以给代理添加“属性”。

为什么不能直接添加属性?

  • 协议(Protocol):delegate 通常是通过协议来实现的,而协议只能声明属性的 gettersetter 方法,并不能直接提供存储功能。
  • 类扩展(Class Extension)和分类(Category):虽然类扩展和分类可以给现有类添加新的方法,但只有类扩展能添加私有属性(在实现文件中声明)。分类则不能添加存储属性,因为它们不拥有实例变量。

间接为 delegate 添加属性的方法:

使用关联对象(Associated Objects)

关联对象是一种使用 Objective-C Runtime 来为现有类添加存储属性的技术。这对于给系统类或其他不能直接修改的类添加属性非常有用。

示例:为一个遵循 MyDelegate 协议的 delegate 添加一个字符串属性 customProperty

#import <objc/runtime.h>

// 定义关键字,用于关联对象时作为唯一标识符
static const char *CustomPropertyKey = "CustomPropertyKey";

@interface NSObject (MyDelegateCustomProperty)

@property (nonatomic, strong) NSString *customProperty;

@end

@implementation NSObject (MyDelegateCustomProperty)

- (NSString *)customProperty {
    return objc_getAssociatedObject(self, CustomPropertyKey);
}

- (void)setCustomProperty:(NSString *)customProperty {
    objc_setAssociatedObject(self, CustomPropertyKey, customProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

通过这种方式,你可以给任何遵循 MyDelegate 协议的对象添加 customProperty 属性,而不需要修改原始协议定义。这在扩展现有库或框架的功能时特别有用。

其他关联对象的知识,请看下一章~
关联对象介绍


4. 问:delegate不是代理设计模式?

MJ说过,delegate不是代理设计模式,NSProxy是代理设计模式
于海说delegate是代理设计模式
那么,delegate是不是代理设计模式呢?

在 iOS 开发中,delegate 主要是应用了代理模式(Delegation Pattern)的概念,而 NSProxy 则是一个应用了代理模式以及虚拟代理模式(Virtual Proxy Pattern) 的案例。下面是对两者的详细解释:

Delegate (代理模式)

代理模式是一种结构型设计模式,它允许对象将一些操作委托给另一个对象来执行。在 iOS 中,delegate 是一种实现该模式的机制,使得一个对象(称为委托者)可以将在某些事件发生时应该执行的决策或行为外包给另一个对象(称为代理)。这种模式主要用于以下目的:

  • 回调机制:允许一个对象通知另一个对象某个事件已经发生。
  • 解耦:帮助减少对象间的耦合度,使得它们可以更容易地被重用。
  • 定制行为:在不改变原有类的代码的情况下,通过代理提供定制的行为。

为什么是代理模式?因为它允许一个对象(委托者)定义一系列操作,在需要的时候由另一个对象(代理)来实现,这正是代理模式的核心思想。

NSProxy (代理模式和虚拟代理模式)

NSProxy 是 Objective-C 中的一个抽象类,它提供了一个接口,允许创建代表其他对象或者是为其他对象提供服务的代理对象。NSProxy 可以用于多种设计模式,但主要是:

  • 代理模式:通过为其他对象提供代理,执行操作或者拦截操作,然后再传递给真实对象。
  • 虚拟代理模式:延迟创建或加载一个昂贵的对象直到真正需要的时候。通过 NSProxy 子类化,可以创建一个轻量级代理对象,当实际对象的方法或属性被访问时,才创建或加载实际对象。

NSProxy 之所以与虚拟代理模式关联,是因为它常用于控制对另一个对象的访问,可以在实际对象被需要之前,不执行实际对象的创建。这样,NSProxy 可以代表一个需要大量资源或需要从网络加载的对象,直到这个对象真正被需要,才去创建或加载,从而节省资源。

综上,delegate 主要实现了代理模式,而 NSProxy 既可以应用代理模式也可以应用虚拟代理模式,根据它们提供的功能和用途不同。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值