代理分三个模块:协议、委托、代理
协议,就是@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");
}
代理的本质,其实是:代理方内存地址的传递
我们在代理方里面写这一行代码:
委托方.delegate = self;
将self
的内存地址,赋值给委托方.delegate
则委托方.delegate
的内存地址也是self
代理方的内存地址
然后,在委托方执行代理方法:
if([self.delegate respondsToSelector:@selector(delegateAction)]){
[self.delegate delegateAction];
}
此时,self.delegate
=委托方.delegate
=代理方
,即代理方的地址
也就是,执行[代理方 delegateAction]
iOS开发-消息传递方式-代理(delegate)及协议(protocol)
针对代理,有几个问题:
主要是:委托方.delegate = self
这句话导致的
如何理解一对多?
是:委托方执行一个代理方法,代理方执行对个方法?
比如,控制器B调用代理方法,使得控制器A上面三个图片分别赋值
这样的话,完全可以一个方法完成,做判断即可
或者,执行三个代理方法,控制器A上实现三个方法即可
还是:委托方执行一个代理方法,多个代理方响应该方法?
实现代理一对多,是说:同一个代理,被多个对象监听
将代理设置成数组、集合,而不是单单的一个weak属性值
首先定义一个协议,声明需要多个对象响应的方法。
@protocol CustomDelegate <NSObject>
@optional
- (void)doSomething;
@end
然后,创建一个类来管理多个 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
在每个代理对象中,实现:
//引入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];
}
上面的代码就必须写,不然,代理方不会被释放
不过,最好使用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 {
}
(
"" ,
""
)
从结果可以看出,viewDidLoad大括号运行完毕:
由于weakHashTable不能强引用里面的对象,因此,NSHashTable里面的数据为空
由于NSMutableArray可以强引用里面的对象,因此,NSMutableArray里面的数据不为空
NSMapTable 对应 NSMutableDictionary
NSHashTable 对应 NSMutableSet
NSPointerArray 对应 NSMutableArray
iOS开发-NSMapTable NSHashTable NSPointerArray的使用
利用消息转发机制
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
然后是调用:
一个委托者(例如,一个事件发布者)和多个代理(例如,事件订阅者)的交互。
假设有一个简单的事件协议,以及如何在事件发生时通知所有注册的代理。
首先,定义一个事件协议EventDelegate
,该协议包含委托者将调用的方法。
// EventDelegate.h
#import <Foundation/Foundation.h>
@protocol EventDelegate <NSObject>
@optional
- (void)eventDidHappenWithData:(NSDictionary *)data;
@end
这个协议声明了一个方法eventDidHappenWithData:
,任何想要监听事件的代理都应该实现这个方法。
然后,创建一个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
创建两个简单的代理类,它们都遵循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 重复上述过程...
最后,展示如何将所有这些组件连接起来,触发事件并通知所有的代理。
#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
),创建并注册两个事件监听者(EventListener1
和EventListener2
),以及如何触发一个事件并通过DelegateManager
将其通知给所有注册的代理。每个监听器在接收到事件时都会打印一条消息,显示它已经收到了包含特定数据的事件通知。
以上就是如何使用runtime来实现delegate一对多模式的示例。这样每次 delegate 调用方法的时候,会被 DelegateManager 截获并转发给所有注册的对象。
最主要的就是,调用[publisher triggerEvent];
方法,里面会执行
self.delegateManager eventDidHappenWithData:eventData
,报方法找不到,因此会走消息发送机制,又因为manager是继承NSProxy,因此,直接走方法签名
然后在方法签名里面,for循环,取出数组元素,调用元素对应的协议方法
相当于数组里面的对象,都执行了一遍协议,也就完成了代理一对多
在 Objective-C 中,delegate
层面并不直接支持添加属性。这是因为 delegate
通常被定义为遵循某个协议的对象,而协议自身只用于声明方法和属性的接口,并不实现存储属性。但是,有几种间接的方法可以给代理添加“属性”。
delegate
通常是通过协议来实现的,而协议只能声明属性的 getter
和 setter
方法,并不能直接提供存储功能。使用关联对象(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
属性,而不需要修改原始协议定义。这在扩展现有库或框架的功能时特别有用。
其他关联对象的知识,请看下一章~
关联对象介绍
MJ说过,delegate不是代理设计模式,NSProxy是代理设计模式
于海说delegate是代理设计模式
那么,delegate是不是代理设计模式呢?
在 iOS 开发中,delegate
主要是应用了代理模式(Delegation Pattern)的概念,而 NSProxy
则是一个应用了代理模式以及虚拟代理模式(Virtual Proxy Pattern) 的案例。下面是对两者的详细解释:
代理模式是一种结构型设计模式,它允许对象将一些操作委托给另一个对象来执行。在 iOS 中,delegate
是一种实现该模式的机制,使得一个对象(称为委托者)可以将在某些事件发生时应该执行的决策或行为外包给另一个对象(称为代理)。这种模式主要用于以下目的:
为什么是代理模式?因为它允许一个对象(委托者)定义一系列操作,在需要的时候由另一个对象(代理)来实现,这正是代理模式的核心思想。
NSProxy
是 Objective-C 中的一个抽象类,它提供了一个接口,允许创建代表其他对象或者是为其他对象提供服务的代理对象。NSProxy
可以用于多种设计模式,但主要是:
NSProxy
子类化,可以创建一个轻量级代理对象,当实际对象的方法或属性被访问时,才创建或加载实际对象。NSProxy
之所以与虚拟代理模式关联,是因为它常用于控制对另一个对象的访问,可以在实际对象被需要之前,不执行实际对象的创建。这样,NSProxy
可以代表一个需要大量资源或需要从网络加载的对象,直到这个对象真正被需要,才去创建或加载,从而节省资源。
综上,delegate
主要实现了代理模式,而 NSProxy
既可以应用代理模式也可以应用虚拟代理模式,根据它们提供的功能和用途不同。