『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];
未完待续。。。
网友评论