由于通知的一对多实现会产生内存泄漏、耦合关系太离散等一系列问题,有了想用代理实现一对多的方式通知。
需求:当多个类都服从了某个协议并且这些类实现了协议中同一个方法时,触发其中一个代理,所有类都将触发该代理方法。
思路:创建该代理的管理类,用于记录实现该类的target和实现消息转发。
// 把目标类添加到管理类里面
- (void)addTarget:(id)target protcol:(id)protocol {
if (![self.refTargets.allKeys containsObject:protocol]) {
NSPointerArray *targets = [NSPointerArray weakObjectsPointerArray];
[targets addPointer:(__bridge void * _Nullable)(target)];
[self.refTargets setObject:targets forKey:protocol];
}
else {
NSPointerArray *targets = [self.refTargets objectForKey:protocol];
if ([targets.allObjects containsObject:target]) {
return;
}
[targets addPointer:(__bridge void * _Nullable)(target)];
}
}
// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
for (id key in self.refTargets.allKeys) {
NSPointerArray *targets = [self.refTargets objectForKey:key];
for (id obj in targets) {
if ((sig = [obj methodSignatureForSelector:aSelector])) {
break;
}
}
}
}
return sig;
}
// 方法转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
for (NSString* key in self.refTargets.allKeys) {
NSPointerArray *targets = [self.refTargets objectForKey:key];
const char *name = [key UTF8String];
Protocol *protocol = objc_getProtocol(name);
NSAssert(protocol!=nil, @"不存在的协议");
struct objc_method_description protocol_method_description = protocol_getMethodDescription(protocol, anInvocation.selector, YES, YES);
if (protocol_method_description.name!=nil) {
for (id obj in targets) {
if ([obj respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:obj];
}
}
}
}
}
// 移除某个目标类
- (void)removeProtocol:(id)protocol{
if ([self.refTargets.allKeys containsObject:protocol]) {
[_refTargets removeObjectForKey:protocol];
}
}
// 移除所有目标类
- (void)removeAll{
[self.refTargets removeAllObjects];
}
// 判段目标类否是实现该协议
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector]) {
return YES;
}
for (id key in self.refTargets.allKeys) {
NSPointerArray *targets = [self.refTargets objectForKey:key];
for (id obj in targets) {
if ([obj respondsToSelector:aSelector]) {
return YES;
}
}
}
return NO;
}