iOS-消息转发和应用

对象收到一个它无法响应的方法到崩溃;

消息转发机制共分为3大步骤:

1、动态解析。先询问接受者的类所属的类,看看有没有动态的添加方法。resolveInstanceMethod,resolveClassMethod

2、快速消息转发。如果1执行完了也没有动态新增方法,那运行期系统就请接收者看看有没有其他对象能处理这条有消息,有的话就把消息转发给哪个对象。forwardingTargetForSelector

3、完整的消息转发机制。如果2没有,那运行期系统会把消息有关的全部细节写到NSInvocation对象里面。首先签名NSMethodSignature,然后forwardInvocation处理NSInvocation对象。

第一阶段:

如果调用了对象方法首先会进行+(BOOL)resolveInstanceMethod:(SEL)sel判断
如果调用了类方法 首先会进行 +(BOOL)resolveClassMethod:(SEL)sel判断
两个方法都为类方法,如果YES则能接受消息 NO不能接受消息 进入第二步

 

  • resolveInstanceMethod

当根据selector没有找到对应的method时,首先会调用这个方法,在该方法中你可以为一个类添加一个方法。并返回yes。下面的代码只是声明了runTo方法,没有实现。

//Car.h

@interface Car : NSObject

- (void)runTo:(NSString *)place;

@end

 

//Car.m

@implementation Car

+ (BOOL)resolveInstanceMethod:(SEL)sel{

    if (sel == @selector(runTo:)) {

        class_addMethod(self, sel, (IMP)dynamicMethodIMPRunTo, "v@:@");

        return YES;

    }

    return [super resolveInstanceMethod:sel];

}

//动态添加的@selector(runTo:) 对应的实现

static void dynamicMethodIMPRunTo(id self, SEL _cmd,id place){

    NSLog(@"dynamicMethodIMPRunTo %@",place);

}

@end

 

第二阶段:

  • forwardingTargetForSelector

如果resolveInstanceMethod没有实现,返回No,或者没有动态添加方法的话,就会执行forwardingTargetForSelector。 在这里你可以返回一个能够执行这个selector的对象otherTarget,接下来消息会重新发送到这个otherTarget。

//Person.h

@interface Person : NSObject

- (void)runTo:(NSString *)place;

@end

 

//Person.m

@implementation Person

- (void)runTo:(NSString *)place;{

    NSLog(@"person runTo %@",place);

}

@end

 

//Car.h

@interface Car : NSObject

- (void)runTo:(NSString *)place;

@end

 

//Car.m

@implementation Car

- (id)forwardingTargetForSelector:(SEL)aSelector{

    //将消息转发给Person的实例

    if (aSelector == @selector(runTo:)){

        return [[Person alloc]init];

    }

    return [super forwardingTargetForSelector:aSelector];

}

@end

 

第三阶段

  • forwardInvocation

如果上面两种情况没有执行,就会执行通过forwardInvocation进行消息转发。首先签名NSMethodSignature,然后forwardInvocation处理NSInvocation对象。

@implementation Car

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{

    //判断selector是否为需要转发的,如果是则手动生成方法签名并返回。

    if (aSelector == @selector(runTo:)){

        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];

    }

    return [super forwardingTargetForSelector:aSelector];

}

 

- (void)forwardInvocation:(NSInvocation *)anInvocation{

    //判断待处理的anInvocation是否为我们要处理的

    if (anInvocation.selector == @selector(runTo:)){

    

    }else{

    }

}

@end

 

在NSInvocation对象中保存着我们调用一个method的所有信息。可以看下其属性和方法:

  • methodSignature 含有返回值类型,参数个数及每个参数的类型 等信息。
  • - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;获取调用method时传的参数
  • - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx; 设置第index参数。
  • - (void)invoke; 开始执行
  • - (void)getReturnValue:(void *)retLoc; 获取返回值

下面的代码演示如何获取调用method时所传的各参数值

- (void)forwardInvocation:(NSInvocation *)anInvocation{

    if (anInvocation.selector == @selector(runTo:)){

        void *argBuf = NULL;

        NSUInteger numberOfArguments = anInvocation.methodSignature.numberOfArguments;

        for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {

            const char *type = [anInvocation.methodSignature getArgumentTypeAtIndex:idx];

            NSUInteger argSize;

            NSGetSizeAndAlignment(type, &argSize, NULL);

            if (!(argBuf = reallocf(argBuf, argSize))) {

                NSLog(@"Failed to allocate memory for block invocation.");

                return ;

            }

            

            [anInvocation getArgument:argBuf atIndex:idx];

            //现在argBuf 中保存着第index 参数的值。 你可以使用这些值进行其他处理,例如为block中各参数赋值,并调用。

        }

    }else{

        

    }

}

 

##通过手动触发消息转发(method已经实现) 前面所描述的消息转发都是在selector没有对应实现时自动进行的,我们称之为自动消息转发。现在有个需求:即使Car类实现了 runTo:,执行[objOfCar runTo:@"shangHai"]; 时也进行消息转发(手动触发),如何实现? 实现方法如下:利用 method swizzling 将selector的实现改变为_objc_msgForward或者_objc_msgForward_stret。在调selector时就会进行消息转发。 看下面的代码:

//对 runTo: 进行消息转发

@implementation Car

 

//进行 method swizzling。此时调用runTo:就会进行消息转发

+ (void)load{

    SEL selector = @selector(runTo:);

    Method targetMethod = class_getInstanceMethod(self.class, @selector(selector));

    const char *typeEncoding = method_getTypeEncoding(targetMethod);

    IMP targetMethodIMP = _objc_msgForward;

    class_replaceMethod(self.class, selector, targetMethodIMP, typeEncoding);

}

 

- (void)runTo:(NSString *)place{

    NSLog(@"car runTo %@",place);

}

 

//消息转发,调用这个方法。anInvocation中保存着调用方法时传递的参数信息

- (void)forwardInvocation:(NSInvocation *)anInvocation{

    if (anInvocation.selector == @selector(runTo:)){

    

    }else{

        

    }

}

 

应用案例:

1.JSPatch --iOS动态化更新方案

具体实现bang神已经在下面两篇博客内进行了详细的讲解,非常精妙的使用了,消息转发机制来进行JS和OC的交互,从而实现iOS的热更新。虽然去年苹果大力整改热更新让JSPatch的审核通过率在有一段时间里面无法过审,但是后面bang神对源码进行代码混淆之后,基本上是可以过审了。不论如何,这个动态化方案都是技术的一次进步,不过目前是被苹果爸爸打压的。不过如果在bang神的平台上用正规混淆版本别自己乱来,通过率还是可以的。有兴趣的同学可以看看这两篇原理文章,这里只摘出来用到消息转发的部分。

 

2.为 @dynamic 实现方法

使用 @synthesize 可以为 @property 自动生成 getter 和 setter 方法(现 Xcode 版本中,会自动生成),而 @dynamic 则是告诉编译器,不用生成 getter 和 setter 方法。当使用 @dynamic 时,我们可以使用消息转发机制,来动态添加 getter 和 setter 方法。当然你也用其他的方法来实现。

 

3.实现多重代理

利用消息转发机制可以无代码侵入的实现多重代理,让不同对象可以同时代理同个回调,然后在各自负责的区域进行相应的处理,降低了代码的耦合程度。用第三阶段实现

 

4.间接实现多继承

Objective-C本身不支持多继承,这是因为消息机制名称查找发生在运行时而非编译时,很难解决多个基类可能导致的二义性问题,但是可以通过消息转发机制在内部创建多个功能的对象,把不能实现的功能给转发到其他对象上去,这样就做出来一种多继承的假象。转发和继承相似,可用于为OC编程添加一些多继承的效果,一个对象把消息转发出去,就好像他把另一个对象中放法接过来或者“继承”一样。消息转发弥补了objc不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值