『ios』进阶

『ios』-objc_msgSend + 消息转发 全面解析(二

2018-09-24  本文已影响7人  butterflyer
1537770302354.jpg

上一篇说了很多objc_msgSend构成一类的东西,这篇说些别的。
废话不多说看下面代码。

int a(id self, SEL _cmd) {
    return 1;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    class_addMethod([self class], @selector(a), (IMP)a, "i@:");
    return YES;
}
@end
int main(int argc, char * argv[]) {
    A *aObject = [[A alloc] init];
    NSLog(@"%ld", aObject.a);
}
void b(id self, SEL _cmd) {
    NSLog(@"b");
}
+ (BOOL)resolveClassMethod:(SEL)sel {
    class_addMethod([self class], @selector(b), (IMP)b, "v@:");
    return YES;
}
@end
int main(int argc, char * argv[]) {
    [[A class] performSelector:@selector(b)]; // 因为[A b];这种调用方式会编译错误,所以动态调用
}

怎么说?明白要说什么吗?动态绑定方法,第一种绑定的.a这个实例方法,第二种,应该也可以看出来是绑定的类方法。那么?
第二种真的可以绑定成功吗?答案是错的。

+ (BOOL)resolveClassMethod:(SEL)sel {
    Class aMeta = objc_getMetaClass(class_getName([self class]));
    class_addMethod([aMeta class], @selector(b), (IMP)b, "v@:");
    return YES;
}

这是正确的方法。为啥?
因为之前说过的,类方法存在mataclass的methodlis中,所以,必须添加到meta中。so。

不知道你有没有注意到这两个方法。

+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel ;

当然啦,这是消息转发的环节开始喽。

下面开始讲消息转发机制。
第一步

+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel ;

这两个方法是消息转发机制的第一步,在这一步我们可以做添加方法,添加对象等操作。如果返回NO。则进入下一步

第二步
这一步我们需要做的是,返回一个可以正确响应消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。

- (id)forwardingTargetForSelector:(SEL)aSelector

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSString *seletorString = NSStringFromSelector(aSelector);
    if ([@"myobjc" isEqualToString:seletorString]) {
        Student *s = [[Student alloc] init];
        return s;
    }
    // 继续转发
    return [super forwardingTargetForSelector:aSelector];
}

第三步
获得方法签名,这里你有机会进一步挽救程序崩溃。
尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

举几个具体的例子
第一个

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        B *bObject = [[B alloc] init];
        signature = [bObject methodSignatureForSelector:aSelector];
    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    B *bObject = [[B alloc] init];
    [anInvocation invokeWithTarget:bObject];
}

第二个,这个例子,可以说讲述了消息转发第三步和第四步的所有过程

// 完整的消息转发
- (void)travel:(NSString*)city
{
    NSLog(@"Teacher travel:%@", city);
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSString *method = NSStringFromSelector(aSelector);
    if ([@"playPiano" isEqualToString:method]) {
        
        NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        return signature;
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = @selector(travel:);
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    anInvocation = [NSInvocation invocationWithMethodSignature:signature];
    [anInvocation setTarget:self];
    [anInvocation setSelector:@selector(travel:)];
    NSString *city = @"北京";
    // 消息的第一个参数是self,第二个参数是选择子,所以"北京"是第三个参数
    [anInvocation setArgument:&city atIndex:2];
    
    if ([self respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:self];
        return;
    } else {
        Student *s = [[Student alloc] init];
        if ([s respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:s];
            return;
        }
    }
    
    // 从继承树中查找
    [super forwardInvocation:anInvocation];
}

第四步
将第三步获取到的方法签名包装成Invocation传入,如何处理就在这里面了,并返回非nil。

- (void)forwardInvocation:(NSInvocation *)anInvocation

第五步 没有找到合适的接受者。崩溃

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    [super doesNotRecognizeSelector:aSelector];
}

ios自习室欢迎进入,一起学习一起进步。

IMG_7291.JPG
上一篇下一篇

猜你喜欢

热点阅读