OC消息转发

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

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

简单介绍消息转发过程

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 YES
	return 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值