『ios』-objc_msgSend + 消息轉(zhuǎn)發(fā) 全面解析(二)
對(duì)于 NSInvocation 之前的意識(shí)一直很模糊构舟,雖然在消息轉(zhuǎn)發(fā)中用過肝箱,但是缺的就是沉下心來,分析一波扶关。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;//防止參數(shù)釋放掉
@property (readonly) BOOL argumentsRetained;
@property (nullable, assign) id target; //target
@property SEL selector;//方法
- (void)getReturnValue:(void *)retLoc;//獲取返回值
- (void)setReturnValue:(void *)retLoc;//設(shè)置返回值
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;//獲取參數(shù)
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;//設(shè)置參數(shù)
- (void)invoke; //執(zhí)行
- (void)invokeWithTarget:(id)target;// target發(fā)送消息残吩,即target執(zhí)行方法
@end
從初始化方法+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;來看生棍,我們需要 NSMethodSignature *這個(gè)對(duì)象。
那么下個(gè)方法簽名
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;//參數(shù)數(shù)量
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;//獲取參數(shù)類型
@property (readonly) NSUInteger frameLength;
- (BOOL)isOneway;// 是否是單向
@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;//返回值類型
@property (readonly) NSUInteger methodReturnLength;//返回長(zhǎng)度
@end
api看完了猪钮,我覺得有些東西還是得從方法中來看才能學(xué)到東西。
-(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個(gè)隱藏參數(shù)胆建,所以 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];
// 需要做參數(shù)類型判斷然后解析成對(duì)應(yīng)類型烤低,這里默認(rèn)所有參數(shù)均為OC對(duì)象
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];
}
// 需要做返回類型判斷。比如返回值為常量需要包裝成對(duì)象笆载,這里僅以最簡(jiǎn)單的`@`為例
return returnVal;
}
直接用po看打印扑馁。
從上面我們可以看到,invocation有四個(gè)參數(shù)凉驻,target selector argument argument.
然后分別對(duì)應(yīng)這四個(gè)符號(hào)腻要。@ : q @.
看到這符號(hào)也許你有點(diǎn)蒙蔽。
NSMethodSignature的初始化方法涝登。
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
那么上面的這些符號(hào)就是types.就拿上面這個(gè)方法舉例子雄家。
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:@"@@:q@"];
第一個(gè)@是返回值NSString *
第二個(gè)@是target
第三個(gè):是Selector
第四個(gè)q 是nsintager
第五個(gè)@是nsstring *
對(duì)了好像還沒有展示NSMethodSignature的結(jié)構(gòu)。
當(dāng)然還有其他兩個(gè)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
說到這其實(shí)對(duì)于NSInvocation應(yīng)該就可以運(yùn)用自如了吧胀滚。
上面的例子中的方法趟济,是為了解決performSelector中的傳值問題的。
實(shí)際項(xiàng)目中咽笼,我們用的地方就應(yīng)該是消息轉(zhuǎn)發(fā)的過程中顷编。
正好看到一個(gè)挺好的例子。
動(dòng)態(tài)實(shí)現(xiàn)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) {
//動(dòng)態(tài)造一個(gè) setter函數(shù)
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
} else {
//動(dòng)態(tài)造一個(gè) getter函數(shù)
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
//拿到函數(shù)名
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:@"set"].location == 0) {
//setter函數(shù)形如 setXXX: 拆掉 set和冒號(hào)
key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
NSString *obj;
//從參數(shù)列表中找到值
[invocation getArgument:&obj atIndex:2];
[data setObject:obj forKey:key];
} else {
//getter函數(shù)就相對(duì)簡(jiǎn)單了媳纬,直接把函數(shù)名做 key就好了。
NSString *obj = [data objectForKey:key];
[invocation setReturnValue:&obj];
}
}
最后,附上某位大神寫的對(duì)于block钮惠,應(yīng)該怎么應(yīng)用
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個(gè)隱藏參數(shù)杨伙,所以 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;
}
// 需要做參數(shù)類型判斷然后解析成對(duì)應(yīng)類型,這里默認(rèn)所有參數(shù)均為OC對(duì)象
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)));
// 需要做返回類型判斷萌腿。比如返回值為常量需要包裝成對(duì)象,這里僅以最簡(jiǎn)單的`@`為例
return returnStr;
}
上面代碼經(jīng)過測(cè)試抖苦,
這個(gè)地方如果不改為__weak的話就會(huì)崩潰毁菱。
下面附上相關(guān)打印
可以看到invocation有三個(gè)參數(shù)。因?yàn)閎lock沒有selector锌历。
上面的這個(gè)地方我注釋掉了贮庞,因?yàn)檫@是私有api。過不了審哦究西。
// const char *_Block_signature(void *);
// const char *signature = _Block_signature((__bridge void *)target);
//
// NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
// NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
未完待續(xù)窗慎。。卤材。