美文网首页
oc 消息机制试用

oc 消息机制试用

作者: nunca | 来源:发表于2018-03-14 15:04 被阅读0次

    由于Objective-C是动态语言,在代码中如果是通过[object method] 形式实现向object发送method消息,如果object无法响应该消息,则编译器会报错,但如果通过[object performSelector:@selector(method)]这类方式,编译器就不会做拦截,只有等到运行时才能确定object是否能响应此消息,若是发现无法响应则会导致程序崩溃,一般抛出(unrecognized selector sent to instance)错误。

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self performSelector:@selector(testMethod)];
    }
    
    2018-03-14 09:50:33.535715+0800 Nunca[7238:216397] -[NcTestThreeVCtr testMethod]: unrecognized selector sent to instance 0x7feaf3c2fb30
    2018-03-14 09:52:27.793877+0800 Nunca[7238:216397] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NcTestVCtr testMethod]: unrecognized selector sent to instance 0x7feaf3c2fb30'
    

    testMethod并未在类中实现,所以程序崩溃。

    下面试图通过runtime中消息机制来对对象无法响应的消息进行动态绑定或转发,在程序调用doesNotRecognizeSelector方法前进行拦截。

    一、.动态绑定
    1.通过resolveInstanceMethod:(一般用于实现@dynamic属性)

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self performSelector:@selector(testMethod)];
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (![self respondsToSelector:sel]  ) {
            class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    void dynamicMethodIMP (id self, SEL _cmd) {
        NSLog(@"%@----", NSStringFromSelector(_cmd));
    }
    
    2018-03-14 09:52:36.409159+0800 Nunca[883:10041] testMethod----
    

    testMethod并未在类中实现,但可以通过动态绑定为此方法绑定实现地址dynamicMethodIMP(IMP是一个函数指针,指向对应方法的实现部分),类中所有未实现方法的调用都可以通过此方法进行拦截,防止崩溃。

    二、消息转发
    1.通过forwardingTargetForSelector (可实现多重继承)

    @interface NcTestClass ()
    @end
    
    @implementation NcTestClass
    - (void)testMethod {
        NSLog(@"testMethod in NcTestClass----");
    }
    @end
    
    @interface NcTestVCtr ()
    @end
    
    @implementation NcTestVCtr
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self performSelector:@selector(testMethod)];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector  {
        if ([NSStringFromSelector(aSelector) isEqualToString:@"testMethod"] ) {
            NSLog(@"forwarding selector to NcTestClass----");
            return [[NcTestClass alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    @end
    
    
    2018-03-14 10:52:33.267644+0800 Nunca[2400:43738] forwarding selector to NcTestClass----
    2018-03-14 10:52:33.268506+0800 Nunca[2400:43738] testMethod in NcTestClass----
    

    testMethod并未在NcTestVCtr中实现,NcTestVCtr通过forwardingTargetForSelector方法将testMethod转发至NcTestClass,最终NcTestClass中的testMethod被执行
    forwardingTargetForSelector无法操作转发过程中的参数与返回值

    2.通过forwardInvocation: (完整消息转发)
    在调用forwardInvocation: 方法之前系统会先调用methodSignatureForSelector:方法,若methodSignatureForSelector: 返回值为nil 则会触发doesNotRecognizeSelector抛出异常然后crash,只有当返回签名不为空才会继续调用forwardInvocation: 进行消息转发

    @interface NcTestClass ()
    @end
    
    @implementation NcTestClass
    - (void)testMethod {
        NSLog(@"testMethod in NcTestClass----");
    }
    @end
    
    @interface NcTestThreeVCtr ()
    @end
    
    @implementation NcTestThreeVCtr
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self performSelector:@selector(testMethod)];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (!signature) {
            if ([NcTestClass instancesRespondToSelector:aSelector]) {
                signature = [NcTestClass instanceMethodSignatureForSelector:aSelector];
            }
        }
        return signature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"forwardInvocation ----");
    }
    
    2018-03-14 14:17:56.303144+0800 Nunca[6479:168308] forwardInvocation ----
    

    此时程序不崩溃(若forwardInvocation:方法未在类中实现则会导致崩溃),但由于只进行了签名 并未进行转发,所以NcTestClass中的testMethod也不会被执行

    修改forwardInvocation:中的代码:

    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"forwardInvocation ----");
        if ([NcTestClass instancesRespondToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:[[NcTestClass alloc] init]];
        }
    }
    

    打印运行结果:

    2018-03-14 14:25:50.562469+0800 Nunca[6606:175681] forwardInvocation ----
    2018-03-14 14:25:50.562643+0800 Nunca[6606:175681] testMethod in NcTestClass----
    

    此时消息转发成功

    以上都是针对实例方法的消息绑定与转发,针对类方法也有相对应的绑定与转发方法,大体相同,就不一一罗列了。

    相关文章

      网友评论

          本文标题:oc 消息机制试用

          本文链接:https://www.haomeiwen.com/subject/hnwxqftx.html