iOS开发之常用技术点『ios』进阶iOS

『ios』NSInvocation NSMethodSignat

2018-09-28  本文已影响77人  butterflyer

『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
上一篇下一篇

猜你喜欢

热点阅读