这篇博文是个人另外一篇 Aspects源码剖析中的一部分,考虑到这部份内容相对独立,单独成篇以便查询。html
使用
NSMethodSignature
和NSInvocation
不只能够完成对method的调用,也能够完成block的调用。在Aspect中,正是运用NSMethodSignature
,NSInvocation
实现了对block的统一处理。这篇博文将演示NSMethodSignature
和NSInvocation
的使用方法及如何使用他们执行method 或 block。ios
##对象调用method代码示例 一个实例对象能够经过三种方式调用其方法。编程
- (void)test{
//type1
[self printStr1:@"hello world 1"];
//type2
[self performSelector:@selector(printStr1:) withObject:@"hello world 2"];
//type3
//获取方法签名
NSMethodSignature *sigOfPrintStr = [self methodSignatureForSelector:@selector(printStr1:)];
//获取方法签名对应的invocation
NSInvocation *invocationOfPrintStr = [NSInvocation invocationWithMethodSignature:sigOfPrintStr];
/**
设置消息接受者,与[invocationOfPrintStr setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等价
*/
[invocationOfPrintStr setTarget:self];
/**设置要执行的selector。与[invocationOfPrintStr setArgument:@selector(printStr1:) atIndex:1] 等价*/
[invocationOfPrintStr setSelector:@selector(printStr1:)];
//设置参数
NSString *str = @"hello world 3";
[invocationOfPrintStr setArgument:&str atIndex:2];
//开始执行
[invocationOfPrintStr invoke];
}
- (void)printStr1:(NSString*)str{
NSLog(@"printStr1 %@",str);
}
复制代码
在调用test方法时,会分别输出:api
2017-01-11 15:20:21.642 AspectTest[2997:146594] printStr1 hello world 1
2017-01-11 15:20:21.643 AspectTest[2997:146594] printStr1 hello world 2
2017-01-11 15:20:21.643 AspectTest[2997:146594] printStr1 hello world 3
复制代码
type1和type2是咱们经常使用的,这里不在赘述,咱们来讲说type3。 NSMethodSignature
和NSInvocation
是Foundation
框架为咱们提供的一种调用方法的方式,常常用于消息转发。数组
##NSMethodSignature概述bash
NSMethodSignature
用于描述method
的类型信息:返回值类型,及每一个参数的类型。 能够经过下面的方式进行建立:数据结构
@interface NSObject
//获取实例方法的签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//获取类方法的签名
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
@end
-------------
//使用ObjCTypes建立方法签名
@interface NSMethodSignature
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@end
复制代码
使用NSObject
的实例方法和类方法建立NSMethodSignature
很简单,不说了。咱撩一撩signatureWithObjCTypes
。 在OC中,每一种数据类型能够经过一个字符编码来表示(Objective-C type encodings)。例如字符‘@’表明一个object, 'i'表明int。 那么,由这些字符组成的字符数组就能够表示方法类型了。举个例子:上面提到的printStr1:
对应的ObjCTypes 为 v@:@。app
- (void)printStr1:(NSString*)str
中的str。printStr1:
原本是一个参数,ObjCTypes怎么成了三个参数?要理解这个还必须理解OC中的消息机制。一个method对应的结构体以下,ObjCTypes中的参数其实与IMP method_imp
函数指针指向的函数的参数相一致。相关内容有不少,不了解的能够参考这篇文章方法与消息。框架
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
复制代码
##NSInvocation概述ide
就像示例代码所示,咱们能够经过+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
建立出NSInvocation
对象。接下来你设置各个参数信息, 而后调用invoke进行调用。执行结束后,经过- (void)getReturnValue:(void *)retLoc;
获取返回值。 这里须要注意,对NSInvocation
对象设置的参数个数及类型和获取的返回值的类型要与建立对象时使用的NSMethodSignature
对象表明的参数及返回值类型向一致,不然cresh。
##使用NSInvocation调用block 下面展现block 的两种调用方式
- (void)test{
void (^block1)(int) = ^(int a){
NSLog(@"block1 %d",a);
};
//type1
block1(1);
//type2
//获取block类型对应的方法签名。
NSMethodSignature *signature = aspect_blockMethodSignature(block1);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:block1];
int a=2;
[invocation setArgument:&a atIndex:1];
[invocation invoke];
}
复制代码
type1 就是经常使用的方法,再也不赘述。看一下type2。 type2和上面调用method的type3用的同样的套路,只是参数不一样:由block生成的NSInvocation对象的第一个参数是block自己,剩下的为 block自身的参数。
因为系统没有提供获取block的ObjCTypes的api,咱们必须想办法找到这个ObjCTypes,只有这样才能生成NSMethodSignature对象! ###block的数据结构 & 从数据结构中获取 ObjCTypes oc是一门动态语言,经过编译 oc能够转变为c语言。通过编译后block对应的数据结构是struct。(block中技术点仍是挺过的,推荐一本书“Objective-C 高级编程”)
//代码来自 Aspect
// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature = (1 << 30)
};
typedef struct _AspectBlock {
__unused Class isa;
AspectBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _AspectBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires AspectBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires AspectBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *AspectBlockRef;
复制代码
在此结构体中 const char *signature
字段就是咱们想要的。经过下面的方法获取signature
并建立NSMethodSignature
对象。
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
AspectBlockRef layout = (__bridge void *)block;
if (!(layout->flags & AspectBlockFlagsHasSignature)) {
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
desc += 2 * sizeof(void *);
}
if (!desc) {
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
复制代码