『ios』NSInvocation NSMethodSignat

作者: butterflyer | 来源:发表于2018-09-28 16:17 被阅读77次

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

    timg.jpg

    对于 NSInvocation 之前的意识一直很模糊,虽然在消息转发中用过,但是缺的就是沉下心来,分析一波。now,let's go.

    先来分析一波api

    @class NSMethodSignature;
    
    NS_ASSUME_NONNULL_BEGIN
    
    NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
    @interface NSInvocation : NSObject {
    @private
        void *_frame;
        void *_retdata;
        id _signature;
        id _container;
        uint8_t _retainedArgs;
        uint8_t _reserved[15];
    }
    
    + (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;//初始化方法
    
    @property (readonly, retain) NSMethodSignature *methodSignature;//方法签名
    
    - (void)retainArguments;//防止参数释放掉
    @property (readonly) BOOL argumentsRetained;
    
    @property (nullable, assign) id target; //target
    @property SEL selector;//方法
    
    - (void)getReturnValue:(void *)retLoc;//获取返回值
    - (void)setReturnValue:(void *)retLoc;//设置返回值
    
    - (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;//获取参数
    - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;//设置参数
    
    - (void)invoke; //执行
    - (void)invokeWithTarget:(id)target;// target发送消息,即target执行方法
    
    @end
    
    

    从初始化方法+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;来看,我们需要 NSMethodSignature *这个对象。
    那么下个方法签名

    NS_ASSUME_NONNULL_BEGIN
    
    NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
    @interface NSMethodSignature : NSObject {
    @private
        void *_private;
        void *_reserved[5];
        unsigned long _flags;
    }
    
    + (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;//初始化方法
    
    @property (readonly) NSUInteger numberOfArguments;//参数数量
    - (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;//获取参数类型
    
    @property (readonly) NSUInteger frameLength;
    
    - (BOOL)isOneway;// 是否是单向
    
    @property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;//返回值类型
    @property (readonly) NSUInteger methodReturnLength;//返回长度
    
    @end
    

    api看完了,我觉得有些东西还是得从方法中来看才能学到东西。

    -(NSString *)doit:(NSInteger)test1 doit2:(NSString *)test2{
        return @"2";
    }
    
    - (id)performSelector:(SEL)aSelector withArguments:(NSArray *)arguments {
        
        if (aSelector == nil) return nil;
        NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector]; //
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        invocation.target = self;
        invocation.selector = aSelector;
        
        // invocation 有2个隐藏参数,所以 argument 从2开始
        if ([arguments isKindOfClass:[NSArray class]]) {
            NSInteger count = MIN(arguments.count, signature.numberOfArguments - 2);
            for (int i = 0; i < count; i++) {
                const char *type = [signature getArgumentTypeAtIndex:2 + I];
                
                // 需要做参数类型判断然后解析成对应类型,这里默认所有参数均为OC对象
                if (strcmp(type, "@") == 0) {
                    id argument = arguments[I];
                    [invocation setArgument:&argument atIndex:2 + I];
                }
            }
        }
        
        [invocation invoke];
        
        id returnVal;
        if (strcmp(signature.methodReturnType, "@") == 0) {
            [invocation getReturnValue:&returnVal];
        }
        // 需要做返回类型判断。比如返回值为常量需要包装成对象,这里仅以最简单的`@`为例
        return returnVal;
    }
    
    

    直接用po看打印。


    image.png

    从上面我们可以看到,invocation有四个参数,target selector argument argument.
    然后分别对应这四个符号。@ : q @.

    看到这符号也许你有点蒙蔽。

    NSMethodSignature的初始化方法。

    + (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
    

    那么上面的这些符号就是types.就拿上面这个方法举例子。

     NSMethodSignature *signature =  [NSMethodSignature signatureWithObjCTypes:@"@@:q@"];
    第一个@是返回值NSString *
    第二个@是target
    第三个:是Selector
    第四个q 是nsintager
    第五个@是nsstring *
    
    

    对了好像还没有展示NSMethodSignature的结构。


    image.png

    当然还有其他两个

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
    
    + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
    

    说到这其实对于NSInvocation应该就可以运用自如了吧。
    上面的例子中的方法,是为了解决performSelector中的传值问题的。


    image.png

    实际项目中,我们用的地方就应该是消息转发的过程中。

    正好看到一个挺好的例子。
    动态实现set get方法。

         data = [[NSMutableDictionary alloc] init];
            [data setObject:@"Tom Sawyer" forKey:@"title"];
            [data setObject:@"Mark Twain" forKey:@"author"];
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
    {
        NSString *sel = NSStringFromSelector(selector);
        if ([sel rangeOfString:@"set"].location == 0) {
            //动态造一个 setter函数
            return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        } else {
            //动态造一个 getter函数
            return [NSMethodSignature signatureWithObjCTypes:"@@:"];
        }
    }
     
    - (void)forwardInvocation:(NSInvocation *)invocation
    {
        //拿到函数名
        NSString *key = NSStringFromSelector([invocation selector]);
        if ([key rangeOfString:@"set"].location == 0) {
            //setter函数形如 setXXX: 拆掉 set和冒号 
            key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
            NSString *obj;
            //从参数列表中找到值
            [invocation getArgument:&obj atIndex:2];
            [data setObject:obj forKey:key];
        } else {
            //getter函数就相对简单了,直接把函数名做 key就好了。
            NSString *obj = [data objectForKey:key];
            [invocation setReturnValue:&obj];
        }
    }
     
    

    最后,附上某位大神写的对于block,应该怎么应用

    static id invokeBlock(id block ,NSArray *arguments) {
        if (block == nil) return nil;
        id target = [block  copy];
        
    //    const char *_Block_signature(void *);
    //    const char *signature = _Block_signature((__bridge void *)target);
    //
    //    NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
    //    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
        CTBlockDescription *ct = [[CTBlockDescription alloc] initWithBlock:target];
        NSMethodSignature *methodSignature = ct.blockSignature;
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
        
        invocation.target = target;
        [invocation retainArguments];
        // invocation 有1个隐藏参数,所以 argument 从1开始
        if ([arguments isKindOfClass:[NSArray class]]) {
            NSInteger count = MIN(arguments.count, methodSignature.numberOfArguments - 1);
            for (int i = 0; i < count; i++) {
                const char *type = [methodSignature getArgumentTypeAtIndex:1 + I];
                NSString *typeStr = [NSString stringWithUTF8String:type];
                if ([typeStr containsString:@"\""]) {
                    type = [typeStr substringToIndex:1].UTF8String;
                }
                
                // 需要做参数类型判断然后解析成对应类型,这里默认所有参数均为OC对象
                if (strcmp(type, "@") == 0) {
                    id argument = arguments[I];
                    [invocation setArgument:&argument atIndex:1 + I];
                }
            }
        }
        
        [invocation invoke];
        
       __weak  id  returnVal;
        
    //        printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(returnVal)));
        
        const char *type = methodSignature.methodReturnType;
        NSString *returnType = [NSString stringWithUTF8String:type];
        if ([returnType containsString:@"\""]) {
            type = [returnType substringToIndex:1].UTF8String;
        }
        if (strcmp(type, "@") == 0) {
            [invocation getReturnValue:&returnVal];
        }
        
        NSString *returnStr = returnVal;
       
        printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(returnVal)));
         printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(returnStr)));
         printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(target)));
        // 需要做返回类型判断。比如返回值为常量需要包装成对象,这里仅以最简单的`@`为例
        return returnStr;
    }
    

    上面代码经过测试,


    image.png

    这个地方如果不改为__weak的话就会崩溃。
    下面附上相关打印


    image.png
    可以看到invocation有三个参数。因为block没有selector。
    上面的这个地方我注释掉了,因为这是私有api。过不了审哦。
    //    const char *_Block_signature(void *);
    //    const char *signature = _Block_signature((__bridge void *)target);
    //
    //    NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
    //    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    

    未完待续。。。

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

    IMG_7291.JPG

    相关文章

      网友评论

      • 小丑余:为什么用__weak修饰了之后就不会崩溃了?
        butterflyer:我觉得吧,加上weak之后就会释放之后至为nil。 还在寻找答案我也。

      本文标题:『ios』NSInvocation NSMethodSignat

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