『ios』NSInvocation NSMethodSignature 全面解析

『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];

未完待续。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,859评论 0 9
  • 摘要:Aspects用来干什么?Aspect是一个简洁高效的用于使iOS支持AOP(面向切面编程)的框架。官方描述...
    京北磊哥阅读 892评论 0 1
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,191评论 0 9
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 819评论 0 1
  • 去年有段时间得空,就把谷歌GAE的API权威指南看了一遍,收获颇丰,特别是在自己几乎独立开发了公司的云数据中心之后...
    骑单车的勋爵阅读 20,814评论 0 41