美文网首页iOS进阶
iOS开发 方法签名

iOS开发 方法签名

作者: 霸_霸霸 | 来源:发表于2018-08-15 16:13 被阅读112次

    1. 什么是方法签名?

    Java中的方法签名是由方法名称和一个参数列表(方法的参数的顺序和类型)组成, 不包含方法的返回值.
    iOS中的方法签名是通过NSMethodSignature实现的, 下面我们看看NSMethodSignature的常用方法和属性

    1.1 NSMethodSignature自己的方法

    //手动初始化方法
    + (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
    

    使用:

    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
    

    signatureWithObjCTypes的参数可查看官方文档的相关介绍

    问题----------------

    我们上面说方法的签名是由方法名称和一个参数列表(方法的参数的顺序和类型)组成, 不包含方法的返回值.
    但iOS中的方法签名似乎不一样, 不涉及方法名, 但是包含方法的返回值类型(void), 参数类型(obj, SEL), 真的是这样么??
    我们需要保留这个问题, 在实际使用过程中或许能够明白这个问题.

    1.2 NSObject中的方法

    除了NSMethodSignature类中包含创建NSMethodSignature对象的方法, NSObject中也包含获取NSMethodSignature对象的方法

    //1. 获取类方法或者实例方法签名, 无论是对象还是类对象都能调用该方法
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    //2. 只能获取实例方法签名
    + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
    

    值得注意的是, NSObject中的方法, NSObject的子类都可以调用, 所以一般有三种情况

    1. 从类中获取类方法签名
    2. 从类中获取实例方法签名
    3. 从实例中获取实例方法签名
      但是不能从实例中获取类方法签名, 因为类方法是存放在元类中的, 实例只能获取实例对应的类对象中的方法

    1.2.1 从类中获取类方法签名实例方法签名

        NSMethodSignature *s1 = [NSString methodSignatureForSelector:@selector(alloc)];
        NSMethodSignature *s2 = [NSString methodSignatureForSelector:@selector(init)];
        NSLog(@"--s1:%@",s1);
        NSLog(@"--s2:%@",s2);
    

    结果:

    屏幕快照 2018-08-16 上午9.29.21.png
    我们可以发现, 从不同的方法获取到的方法签名, 为什么是一样的?
    因为方法虽然不同, 但是方法的签名是一样的(同一个NSMethodSignature), 即方法的返回值类型参数类型是一样的

    1.2.2 从实例对象中可获取实例方法签名, 不能获取类方法签名

        NSString *str = @"string...";
        NSMethodSignature *s3 = [str methodSignatureForSelector:@selector(alloc)];
        NSMethodSignature *s4 = [str methodSignatureForSelector:@selector(init)];
        NSLog(@"--s3:%@",s3);
        NSLog(@"--s4:%@",s4);
    

    结果:


    屏幕快照 2018-08-16 上午9.48.33.png

    1.2.3 instanceMethodSignatureForSelector只能获取实例方法的签名

    NSMethodSignature *s5 = [NSString instanceMethodSignatureForSelector:@selector(alloc)];
    NSMethodSignature *s6 = [NSString instanceMethodSignatureForSelector:@selector(init)];
    NSLog(@"--s5:%@",s5);
    NSLog(@"--s6:%@",s6);
    

    结果:


    屏幕快照 2018-08-16 上午9.53.12.png

    2. 如何使用方法签名

    - (void)signatureTest {
        //1. 创建方法签名
        NSMethodSignature *s9 = [NSMethodSignature signatureWithObjCTypes:"v@:ii"];
        //2. 根据方法签名创建一个NSInvocation对象
        NSInvocation *invo = [NSInvocation invocationWithMethodSignature:s9];
        //3. 为NSInvocation对象设置接收消息的对象
        [invo setTarget:self];
        //4. 为NSInvocation对象设置发送的消息
        [invo setSelector:@selector(test11:)];
        //5. 为NSInvocation对象设置一个新的参数
        NSInteger a = 1;
        [invo setArgument:&a atIndex:2];
        //6. 调用NSInvocation
        [invo invoke];
    }
    
    - (void)test11:(int )a{
        NSLog(@"-----:%d",a);
    }
    

    结果:

    2018-08-16 11:19:39.493722+0800 RuntimeNew[4527:236471] -----:1

    解释:

    1. 我们一开始创建的方法签名是有4个参数 @ : i i, 除了前两个是每个方法都有的, 我们又添加了两个int类型的参数, 但是我们的方法- (void)test11:(int )a中并只有一个参数, 依然可以执行, 说明签名函数的参数数量大于被调用的函数参数数量时, 也是可以正常调用的.


    3. iOS中的方法签名是否包含方法名?

    我们可以从两方面去看待这个问题

    3.1 我们可以从上面signatureTest方法的第四步: 为NSInvocation对象设置发送的消息

    [invo setSelector:@selector(test11:)];
    

    我们在已经拥有方法签名的情况下, 还是需要添加Selector, 说明我们还是需要方法名称的, 而不仅仅是返回值类型参数类型

    3.2 这一点, 我们在Runtime的方法转发机制中也有体现

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self performSelector:@selector(testMethod:)];
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return YES;//返回YES,进入下一步转发
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return nil;//返回nil,进入下一步转发
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if ([NSStringFromSelector(aSelector) isEqualToString:@"testMethod:"]) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        SEL sel = anInvocation.selector;
        Person *p = [Person new];
        if([p respondsToSelector:sel]) {
            NSString *str = @"Lucy";
            [anInvocation setArgument:&str atIndex:2];
            [anInvocation invokeWithTarget:p];
        }
        else {
            [self doesNotRecognizeSelector:sel];
        }
    }
    
    1. 我们希望self执行testMethod, 但是self并没有实现该方法, 所以我们要一步一步去找
    2. resolveInstanceMethod:返回YES, 意味着我们没有为testMethod添加函数实现, 执行下一步forwardingTargetForSelector
    3. forwardingTargetForSelector返回nil, 意味着我们没有为该方法提供备用接收者, 所以只能继续执行下一步, 通过方法签名和NSInvocation来实现完整的消息转发
    4. methodSignatureForSelector拿到方法签名---在forwardInvocation指定一个对象, 判断该对象是否实现了此方法. 实现了, 则执行; 未实现, 则调用doesNotRecognizeSelector方法

    问题就出在这一步, 如果我们iOS中的方法签名只看方法的返回值类型参数类型, 我们知道, 一个类中, 有很多方法的返回值类型参数类型是相同的, 这样会出现什么情况? 调用多个方法?
    所以我们必定是要把方法名作为方法签名的重要一部分的.实际上, 我们在forwardInvocation方法中是拿到了方法名的, 我们可以输出一下看看

    SEL sel = anInvocation.selector;
    NSLog(@"%s",anInvocation.selector);
    
    result

    综上, iOS中的方法签名是包含方法名的, 只是有时候不是那么明显

    相关文章

      网友评论

        本文标题:iOS开发 方法签名

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