美文网首页
OC消息转发3部曲

OC消息转发3部曲

作者: 三国韩信 | 来源:发表于2020-06-14 14:29 被阅读0次
    @interface LGPerson : NSObject
    
    -(void)sayNB;
    
    @end
    
    @implementation LGPerson
    
    -(void)sayNB{
        NSLog(@"%s",__func__);
    }
    
    @interface LGTeacher : NSObject
    -(void)sayHello;
    
    @end
    
    @implementation LGTeacher
    -(void)sayHello{
        NSLog(@"+++++++++++%s",__func__);
    }
    @end
    

    这里定义了2个类,LGPerson和LGTeacher。
    然后在main函数里来调用他们

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            LGPerson* p = [[LGPerson alloc]init];
            [p performSelector:@selector(sayLove)];
            //[p performSelector:@selector(sayHello)];
            //[p performSelector:@selector(sayLoving)];
            
        }
        return 0;
    }
    

    很明显LGPerson里没有sayLove这方法,那么当person对象去调用这个方法的时候,正常情况下是会奔溃的,会出现一个oc很经典的错误——方法找不到

    -[LGPerson sayLove]: unrecognized selector sent to instance 0x10050ac90'
    

    那么出现这种情况,开发者该如何预防呢,答案是消息转发3部曲:

    • 动态方法决议 +resolveInstanceMethod或+resolveClassMethod
    • 消息转发1 -forwardingTargetForSelector
    • 消息转发2 -methodSignatureForSelector与-forwardInvocation

    如果以上这3个流程中的任何一个有做了处理,person调用sayLove都不会奔溃

    消息转发.png

    now,show code:

    /*1.动态方法决议:通过对sayLove处理,把另一个对象LGTeacher的sayHello方法的实现赋值给sayLove,
    那么调用sayLove就相当于调用了LGTeacher的sayHello方法.*/
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        if ([NSStringFromSelector(sel) isEqualToString:@"sayLove"]) {
            Method m = class_getInstanceMethod(objc_getClass([@"LGTeacher" UTF8String]), @selector(sayHello));
            const char * types = method_getTypeEncoding(m);
            IMP newImp = class_getMethodImplementation(objc_getClass([@"LGTeacher" UTF8String]), @selector(sayHello));
            class_addMethod(self, sel, newImp, types);
            return YES;
        }
        return  [super resolveInstanceMethod:sel];
    }
    
    /*
    通过对sayLove处理,把另一个对象LGTeacher返回出去,那么系统就会去LGTeacher里找一个叫sayLove的方法,
    如果能找到就调用它,如果返回出去的对象(LGTeacher)也没有sayLove这个方法,那么一样会报错崩溃的。
    */
    -(id)forwardingTargetForSelector:(SEL)aSelector{
        if ([NSStringFromSelector(aSelector) isEqualToString:@"sayLove"]) {
            return [LGTeacher alloc];  
        }
        return [super forwardingTargetForSelector:aSelector];;
    }
    
    /*
    最后就是消息转发最底层的事务转发了,把发送sayLove消息这个事务转发给Invocation去处理,
    如果能Invocation知道某个对象能处理就转发给它,如果没有对象能处理,则丢弃这个事务。
    即不会发生任何操作,也不会报错崩溃。
    相当于[p performSelector:@selector(sayLove)]这句代码不会报错,但也啥都没发生,相当与没写这句代码。
    */
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if ([NSStringFromSelector(aSelector) isEqualToString:@"sayLove"]) {
            //NSMethodSignature * sign = [NSMethodSignature methodSignatureForSelector:aSelector];
            
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];;
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    -(void)forwardInvocation:(NSInvocation *)anInvocation{
        NSLog(@"%s ",__func__);
        if (anInvocation.selector == @selector(sayLoving)) {
            if ([[LGTeacher alloc] respondsToSelector:anInvocation.selector]){
                [anInvocation invokeWithTarget:[LGTeacher alloc]];
            }
        }
    }
    
    

    注:最好的方式是把这些方法都抽取到NSObject的分类中去实现。

    当我们没有做任何处理的时候,系统的forwardInvocation则会去调用doesNotRecognizeSelector函数,在doesNotRecognizeSelector中就会输出unrecognized selector sent to instance那个找不到方法的报错。

    - (void)forwardInvocation:(NSInvocation *)invocation {
        [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
    }
    - (void)doesNotRecognizeSelector:(SEL)sel {
        _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                    object_getClassName(self), sel_getName(sel), self);
    }
    

    相关文章

      网友评论

          本文标题:OC消息转发3部曲

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