消息转发原理
大家都知道OC调用方法,本质上就是发消息,实际上就是调用
objc_msgSend()
方法,一般情况下,对象可以调用本类,父类,类目的方法。但不能调用其他没有继承关系的类的方法,消息转发可以A类的对象调用B类对象的方法。这种机制虽然打破了类的封装性,但却带来了更多的灵活性。
来自NSObject 中的消息转发
- 当A对象在本类 父类 分类中未找到方法的实现,则去下面两个方法查找,是否有动态创建方法实现。
//Dynamically provides an implementation for a given selector for a class method 动态提供类方法实现
+ (BOOL)resolveClassMethod:(SEL)sel;
// Dynamically provides an implementation for a given selector for an instance method// 动态提供一个实例方法的实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;
- 如果没有在1中找到相应的动态实现,则调用下面的方法进行消息转发。既然本类处理不了,那谁能处理就扔给谁喽,和重定向有点类似
// Returns the object to which unrecognized messages should first be directed. 返回一个能处理这个无法识别消息的对象
- (id)forwardingTargetForSelector:(SEL)aSelector
- 如果2 步骤仍然没能处理,则把该消息扔给消息派发系统,该系统的元素包含:target, selector,参数,返回值。NSInvocation对象可以被重复派发给不同的对象,也可以在偷换selector,但是方法签名要相同。XMPPFrameWork中的GCDMulticastDelegate(多播代理)就是利用这一机制实现的,将会在应用部分介绍。
// 将返回一个方法签名,同时抛一个异常,unrecognized selector sent to instance ** 这个异常就是在这里抛的,可以在方方法去掉这个异常,代码就遇到不识别的selector 就不会崩了。不建议这么玩。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 消息派发系统来处理 selector
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- 派发系统也无法处理,调用这个doesNotRecognizeSelector方法
- (void)doesNotRecognizeSelector:(SEL)aSelector
写了个不入流的demo,记录下学习过消息转发。如果想精通消息转发请看腾讯大神的博客,我这篇最多是入门级的。 Objective-C 消息发送与转发机制原理 大神会帮助我们揭秘源码内部是怎么用汇编语言实现消息转发的
消息转发的应用
XMPP的多播代理
- 一般情况,代理只能1V1通信,开源XMPPFramework项目中 GCDMulticastDelegate类使用消息转发实现了多播代理,主要代码:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
for (GCDMulticastDelegateNode *node in delegateNodes)
{ // 兼容Mac
id nodeDelegate = node.delegate;
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
if (nodeDelegate == [NSNull null])
nodeDelegate = node.unsafeDelegate;
#endif
//匹配能处理代理selector的方法签名,即消息要转发给哪些代理
NSMethodSignature *result = [nodeDelegate 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
{
SEL selector = [origInvocation selector];
BOOL foundNilDelegate = NO;
for (GCDMulticastDelegateNode *node in delegateNodes)
{
id nodeDelegate = node.delegate;
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
if (nodeDelegate == [NSNull null])
nodeDelegate = node.unsafeDelegate;
#endif
if ([nodeDelegate respondsToSelector:selector])
{
// All delegates MUST be invoked ASYNCHRONOUSLY.
NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
// 异步执行代理方法
[dupInvocation invokeWithTarget:nodeDelegate];
}});
}
else if (nodeDelegate == nil)
{
foundNilDelegate = YES;
}
}
if (foundNilDelegate)
{
// At lease one weak delegate reference disappeared.
// Remove nil delegate nodes from the list.
//
// This is expected to happen very infrequently.
// This is why we handle it separately (as it requires allocating an indexSet).
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
NSUInteger i = 0;
for (GCDMulticastDelegateNode *node in delegateNodes)
{
id nodeDelegate = node.delegate;
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
if (nodeDelegate == [NSNull null])
nodeDelegate = node.unsafeDelegate;
#endif
if (nodeDelegate == nil)
{
[indexSet addIndex:i];
}
i++;
}
[delegateNodes removeObjectsAtIndexes:indexSet];
}
}
- 多播代理使用 用法比较简单,想要通知就添加代理即可。
- (void)viewDidLoad {
[super viewDidLoad];
// 多播代理
People *p1 = [People new];
Dog *d1 = [Dog new];
GCDMulticastDelegate *mutlDelegate = [[GCDMulticastDelegate alloc]init];
[mutlDelegate addDelegate:p1 delegateQueue:dispatch_get_global_queue(0, 0)];
[mutlDelegate addDelegate:d1 delegateQueue:dispatch_get_global_queue(0, 0)];
// mutlDelegate 并没有实现 test1方法 而是通过消息转发分别让 p1 和 d1 处理test1消息 相当于d1 p1 异步调用自己的test1方法
[mutlDelegate performSelector:@selector(test1)];
}
- demo代码
参考文档
- Objective-C 消息发送与转发机制原理 腾讯大神哦