• Delegate介绍


    代理介绍

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

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

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

    在这里插入图片描述

    代理的用法:

    协议:

    @protocol testDelegate <NSObject>
    @optional
    - (void)delegateAction;
    @end
    
    • 1
    • 2
    • 3
    • 4

    委托方:

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

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

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

    代理方:

    顶部遵守协议:<testDelegate>
    
    //设置委托方的代理为自己
    委托方.delegate = self;
    
    //代理方法的实现
    - (void)delegateAction
    {
        NSLog(@"111111");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    iOS 协议 委托 代理 delegate

    代理的原理

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

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

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

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

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

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


    针对代理,有几个问题:

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

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


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

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

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

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

    方法一:

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

    定义 CustomDelegate 协议

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

    @protocol CustomDelegate <NSObject>
    @optional
    - (void)doSomething;
    @end
    
    • 1
    • 2
    • 3
    • 4
    创建一个管理多个 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    // 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    使用 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];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在委托方:

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

    使用 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];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    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 {
    }
    (
        "",
        ""
    )  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

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

    NSMapTable 对应 NSMutableDictionary
    NSHashTable 对应 NSMutableSet
    NSPointerArray 对应 NSMutableArray
    
    • 1
    • 2
    • 3

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    然后是调用:

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

    步骤 1: 定义事件协议

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

    // EventDelegate.h
    
    #import <Foundation/Foundation.h>
    
    @protocol EventDelegate <NSObject>
    @optional
    - (void)eventDidHappenWithData:(NSDictionary *)data;
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个协议声明了一个方法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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    步骤 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 重复上述过程...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    步骤 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上述代码展示了:如何初始化事件发布者(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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    通过这种方式,你可以给任何遵循 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 既可以应用代理模式也可以应用虚拟代理模式,根据它们提供的功能和用途不同。

  • 相关阅读:
    LC15.三数之和、LC22括号生成
    编译kubeadm使生成证书有效期为100年
    【NeRF】2、NeRF 首篇经典论文介绍(ECCV2020)
    云e办(后端)——根据id查询菜单
    【专题学习】对比学习原理及代码
    java生成PDF的Util
    黑龙江省人口与社会经济数据集(2015-2016年)
    RabbitMQ - Spring boot 整合 RabbitMQ
    2022-A rch安装(详细)
    python+vue+elementui电影个性化推荐系统django协同过滤算法
  • 原文地址:https://blog.csdn.net/IOSSHAN/article/details/137259843