代理容器
在开发过程中,我们可能会遇到这种情况,某一处有变量被修改或某个动作执行完毕时,我们需要通知多个对象做出不同的响应。这种情况下,我们可能首先想的就是使用NSNotificationCenter来实现。
如果需求进一步加深,我们需要这些对象在执行完自己的响应之后有返回值回到发出“通知”的地方。这种情况下如果再使用“通知”就会变得很繁琐,我们需要额外的处理很多“通知”。
这种情况下,我们会期望使用“协议-代理”的方式来进行交互处理,但一般情况下“协议-代理”只被用在一对一的交互关系中,无法适用于此处的一对多关系。那么我们该怎么做呢?
我们来设想如果使用一个容器,来把所有“代理”都装在里面,当我们需要使用回调时,再把它们一个个拿出来进行回调,不就解决了这个问题吗。
int result = 0;
for (id delegate in self.delegateArray) {
result += [delegate doSomething];
}
这样当然可以解决我们最初的问题,但是我们肯定不想在每个需要代理的地方都维护一个数组,并且每次回调时都手写一个循环。所以,我们需要一个新的类来帮助我们管理这个容器。我们姑且叫这个代理管理类为代理容器。
代理容器帮助我们维护了代理数组,但是当我们需要回调时,代理容器如何找到数组里正确的代理,并告诉它调用哪个方法呢,这就需要进行’消息转发‘。
消息转发
我们知道Object—C的方法调用,其实就是依托objc_msgSend()给对象发送了一条消息。对象在收到消息之后,会通过isa找到类的结构体,然后读取method list寻找selector。如果在本类中找不到,则去他的父类的结构体寻找,一直到NSObject为止。如果在NSObject也没有找到需要的selector,这时候就需要开始进行消息转发了。
消息转发的过程被总结为三个步骤,分别是:
1.Method resolution 方法解析处理阶段
2.Fast forwarding 快速转发阶段
3.Normal forwarding 常规转发阶段
Method resolution 方法解析处理阶段
//实例对象调用方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
//类对象调用方法
+ (BOOL)resolveClassMethod:(SEL)sel
对象在method list中找不到对应selector时,会先调用这两个方法。你可以将它理解成,询问你是否添加动态方法来进行处理;如果YES则能接受消息表示你已经添加了方法,将会重新执行objc_msgSend;NO则表示不添加方法,进入消息转发第二步。
Fast forwarding 快速转发阶段
- (id)forwardingTargetForSelector:(SEL)aSelector
既然在第一步已经明确,此对象没有添加新方法响应aSelector,这个方法实际上是让你传递一个可以响应此方法的其它对象。如果你在此返回了一个对象,则会给这个对象发送一个objc_msgSend消息,如果这里返回nil或者self本身,那么则进入消息转发的第三个步骤。
Normal forwarding 常规转发阶段
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
在第二步已经明确没有其他对象可以帮助我们响应这个方法,所以第三部Objec-C又做出了让步。你可以重新制定一个方法,但前提是你需要给出这个方法的方法签名。NSMethodSignature表示方法签名,手写创建比较麻烦,你可以找一下它的规则表。如果你在这里返回了一个方法签名,那么我们还需要在forwardInvocation中来定义如何使用这个方法签名。
至此我们完成了消息转发的所有步骤,如果在这三个步骤都没有处理好找个SEL,那么就会执行doesNotRecognizeSelector方法抛出一个异常。
NSMethodSignature && NSInvocation
这里出现了 NSMethodSignature 和 NSInvocation 这两个类。 这里也稍微聊一下NSInvocation这个类。
我们平时调用方法的写法可能有两种:
[anObject doSomething];
[anObject performSelector:@selector(doSomething) withObject:anArgument];
在不同的情况下,我们会选择合适的调用方法。在此之后,OC还为我们提供了另一种调用方法,就是使用NSInvocation来进行。
// 1.创建方法签名;
NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:@selector(doSomething:)];
// 2.通过方法签名生成NSInvocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
// 3.对invocation设置 方法调用者
invocation.target = self;
// 4.对invocation设置 方法选择器
invocation.selector = @selector(doSomething:);
// 5.对invocation设置 参数
NSString *something = @"do something";
//注意:设置的参数必须从2开始;因为0和1 已经被self ,_cmd 给占用了
[invocation setArgument:&something atIndex:2];
// 6.执行invocation
[invocation invoke];
NSInvocation相关的API还有许多,这里就不赘述了。
GCDMulticastDelegate
我们从XMPP框架里的GCDMulticastDelegate来看一下多重代理的具体实现。
GCDMulticastDelegate中共有 GCDMulticastDelegate、GCDMulticastDelegateEnumerator、GCDMulticastDelegateNode这三个类。我们只要重点看一下GCDMulticastDelegate这个类就可以了。
GCDMulticastDelegate
GCDMulticastDelegate维护了一个delegateNodes数组,当调用addDelegate方法时,就会把真正的代理’delegate‘和一个GCD线程构建成一个GCDMulticastDelegateNode对象添加到数组里面。
- (id)init
{
if ((self = [super init]))
{
delegateNodes = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
if (delegate == nil) return;
if (delegateQueue == NULL) return;
GCDMulticastDelegateNode *node = [[GCDMulticastDelegateNode alloc] init];
node.delegate = delegate;
node.delegateQueue = delegateQueue;
[delegateNodes addObject:node];
}
当我们需要调用代理的协议方法时,我们实际上就是让GCDMulticastDelegate的对象执行某方法。很明显,GCDMulticastDelegate是并没有实现这个方法的。所以接下来我们就要进入消息转发的三个阶段。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
for (GCDMulticastDelegateNode *node in delegateNodes)
{
NSMethodSignature *result = [node.delegate methodSignatureForSelector:aSelector];
if (result != nil)
{
return result;
}
}
// This causes a crash...
// return [super methodSignatureForSelector:aSelector];
// This also causes a crash...
// return nil;
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}
- (void)forwardInvocation:(NSInvocation *)origInvocation
{
@autoreleasepool {
SEL selector = [origInvocation selector];
for (GCDMulticastDelegateNode *node in delegateNodes)
{
id delegate = node.delegate;
if ([delegate respondsToSelector:selector])
{
// All delegates MUST be invoked ASYNCHRONOUSLY.
NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
[dupInvocation invokeWithTarget:delegate];
}});
}
}
}
}
- (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation
{
NSMethodSignature *methodSignature = [origInvocation methodSignature];
NSInvocation *dupInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[dupInvocation setSelector:[origInvocation selector]];
NSUInteger i, count = [methodSignature numberOfArguments];
for (i = 2; i < count; i++)
{
const char *type = [methodSignature getArgumentTypeAtIndex:i];
if (*type == *@encode(BOOL))
{
BOOL value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == *@encode(char) || *type == *@encode(unsigned char))
{
char value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == *@encode(short) || *type == *@encode(unsigned short))
{
short value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == *@encode(int) || *type == *@encode(unsigned int))
{
int value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == *@encode(long) || *type == *@encode(unsigned long))
{
long value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == *@encode(long long) || *type == *@encode(unsigned long long))
{
long long value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == *@encode(double))
{
double value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == *@encode(float))
{
float value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else if (*type == '@')
{
void *value;
[origInvocation getArgument:&value atIndex:i];
[dupInvocation setArgument:&value atIndex:i];
}
else
{
NSString *selectorStr = NSStringFromSelector([origInvocation selector]);
NSString *format = @"Argument %lu to method %@ - Type(%c) not supported";
NSString *reason = [NSString stringWithFormat:format, (unsigned long)(i - 2), selectorStr, *type];
[[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise];
}
}
[dupInvocation retainArguments];
return dupInvocation;
}
从这几个方法中,我们可以出其处理消息转发的逻辑
1. 常规消息转发阶段,在寻找NSMethodSignature方法签名时,会遍历储存Node的数组,寻找可以执行此方法的delegate,获取其方法签名。
2. 在forwardInvocation方法中,首先我们获取到selector,然后遍历数组,寻找可以实现此selector的对象。
3. 利用方法签名生成了一个新的NSNSInvocation对象,然后遍历原origInvocation的参数列表,将所有的参数及其类型押入新的NSInvocation当中。
4. 在Node内存储的GCD线程中,异步调用Invocation的invoke方法,传入参数delegate作为执行这个方法的对象。
这样我们就完成了将一个GCDMulticastDelegate对象无法响应的方法转发给其持有的所有能影响此方法的对象,并在其指定的线程中进行回调。