OC消息转发

来源:互联网 发布:java api接口编写实例 编辑:程序博客网 时间:2024/06/04 19:49

关于消息转发已经是个烂话题,可以搜索下别人的文章

如果没有搜索,这里也会先简单介绍下消息转发概念和过程

简单介绍消息转发过程

oc的方法调用过程是一次消息发送的过程

当类的方法表里没有消息中的选择子时,会有一个消息转发的阶段

第一阶段:可以在该阶段动态添加方法

类方法需要覆盖resolveClassMethod

实例方法需要覆盖resolveInstanceMethod

这两个方法返回前可以为未找到的选择子动态加入方法

+ (BOOL)resolveClassMethod:(SEL)sel{//dynamic add method then return YES//class_addMethod(Class cls, SEL name, IMP imp, const char *types); //return YES;return NO;}- (BOOL)resolveInstanceMethod:(SEL)sel{//dynamic add method then return YESreturn NO;}


第二阶段:交给其他对象处理

- (id)forwardingTargetForSelector:(SEL)aSelector{    //返回一个可以处理选择子的对象    //    return someObjCanHanleThisSelector;    return [super forwardingTargetForSelector:aSelector];}

当然这只是字面上的意思,依然可以动态添加方法后返回自己


第三阶段:拥有完整消息的转发处理

NSInvocation进入头文件可以查看到,这个对象包含并且可以设置一次消息发送的所有要素

- (void)forwardInvocation:(NSInvocation *)anInvocation{    //anInvocation can set anything    [anInvocation invoke];}

还需要配合方法签名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

如果你是新手还不知道如何动态加入方法,一个小提示class_addMethod(Class cls, SEL name, IMP imp, const char *types);xcode进入头文件会有一堆类似的方法

科普时间:

Selector 

简单点说是一个方法的名字,或者说是在某个对象寻找方法实现时的索引

IMP 

一个接受调用者、选择子、方法入参并返回id的函数指针,看下代码:

typedef id (*IMP)(id, SEL, ...); 

可以理解为一个方法真正的实现

NSMethodSignature 

名字这么玄乎,实际是对方法参数的一个包装,例如可以调用

[NSMethodSignature signatureWithObjCTypes:SomeTypeEncoding]

NSInvocation

这是一个消息调用的封装,拥有一次消息发送的所有信息,具体可进入头文件

那么问题来了,如果不希望再看到unrecognized selector...报错怎么办

思路就是让这个消息转发能安全走完(废话- -)

兜住实例方法

这里有一片很仔细的文章,介绍了如何兜住一个实例的方法调用
简单概括下,就是重写某一转发方法,用第三者对象去动态添加来保证完整的转发。
或者load时替换掉某一转发方法再去动态添加。

在上面很仔细的文章里也介绍了消息转让前的方法的寻找过程,这个过程就是下面代码往里走,如果需要验证这个过程,这里简单贴个入口(具体实现有点长,可以进这篇文章底部去下载源码):
IMP class_getMethodImplementation(Class cls, SEL sel){    IMP imp;    if (!cls  ||  !sel) return nil;    imp = lookUpImpOrNil(cls, sel, nil,                          YES/*initialize*/, YES/*cache*/, YES/*resolver*/);    // Translate forwarding function to C-callable external version    if (!imp) {        return _objc_msgForward;    }    return imp;}


那么如何添加类方法?

实验环境

如果需要考虑可行性,可以先看看这里
为了完成这个实验,先写了篇oc里对象、类、元类的简单介绍。
简单贴一下实验环境:
- (void)startTest{    [[self class]performSelector:@selector(goFindNonexistedClassMethod) withObject:nil afterDelay:0];}
+ (void)nonexistedClassMethodSaver {    NSLog(@"recognized selector bla bla");}

+ (BOOL)resolveClassMethod:(SEL)sel{    if ([NSStringFromSelector(sel) isEqualToString:@"goFindNonexistedClassMethod"]) {        //do some thing        return YES;    }    return YES;}
为什么别的转发方法不贴了?因为resolveClassMethod返回NO就崩溃了
思路就是需要在注有"do some thing"的地方加入方法

class_addMethod

博文上面有提到这个方法class_addMethod(Class cls, SEL name, IMP imp, const char *types),它可以为这个类的实例添加方法,实例(至少翻译过来是这样)
那么但需要为类自己添加方法时可不可以用这个方法呢?(这么问肯定是可以,怎么知道的?崩溃多几次)
在多次尝试后,找到了答案,有一个启发来源于巧大大的一篇文章(博客上没找到,去公众号找到了)
里面提到了关于NSObject哪些方法定义由类定义,哪些方法由元类定义。文中提到NSObject类中定义了实例的init等方法,元类中定义了+(id)alloc,+(void)load灯方法

那么大胆尝试下 给这个函数来一一填入参数

给class传入对象的元类
找到IMP [self methodForselector:@selector(nonexistedClassMethodSave)]
找IMP时遇过的小坑错误的调用了class_getMethodImplemention(cls,SEL),这个函数会找实例方法里有没有对应的IMP,当然没有,然后进入-resolveInstanceMethod:,套用NSObject里方法层级的定义,这里传入metaclass试试,结果可行(当然,‘套用’俩字的背后是代码实现,去metaclass的方法列表找到了类方法)
types method_getTypeEncoding(class_getClassMethod(self)) (为什么传self,这个函数会先找metaclass然后搜索)
最后把上面的参数一一填入,结果成功。

P.S. 

欢迎讨论!
最近上架了一个app,可以点击这里查看,实现起来并不难,在体验后如果有需要可以在以后的博文里介绍下实现的心路历程,欢迎反馈
0 0