CTMediator理解
2021-07-06 本文已影响0人
哥只是个菜鸟
1.CTMediator作为路由中间件跳转,很大程度解决了各种文件依赖,更加灵活,比较适用于电商活动等需要各种跳转页面的项目
2.我们需要在外面定义好路由的url和安卓端统一,比如:xxx://shop/detail?id=1234
3.新建一个类,名字按照路由的格式定义,比如:Target_shop,shop表示你要跳转的页面,我这边是购物车页面,里面的函数命名也按照路由定义,比如Action_detail,这个页面暂时没有入参,最终根据传入的url调用这里的方法拿到控制器进行跳转
UIViewController *vc = [[CTMediator sharedInstance] performActionWithUrl:url completion:nil];
[self.navigationController pushViewController:vc animated:YES];
@interface Target_shop : NSObject
- (UIViewController *)Action_detail:(NSDictionary *)params;
@end
@implementation Target_shop
- (UIViewController *)Action_detail:(NSDictionary *)params {
ShopCarViewController *vc = [[ShopCarViewController alloc] init];
vc.hidesBottomBarWhenPushed = YES;
return vc;
}
@end
4.源码实现
- 1.主要还是通过反射机制找到类名Target_shop 和 方法名 Action_detail
NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
// generate target
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
NSObject *target = [self safeFetchCachedTarget:targetClassString];
if (target == nil) {
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
// generate action
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
SEL action = NSSelectorFromString(actionString);
- 2.找到类和方法之后就可以通过 NSInvocation去调用对应的方法,判断它是void,NSInteger,BOOL,CGFloat,NSUInteger,否则使用performSelector , performSelector只能传递2个参数,可以把多个参数封装成NSDictionary,然后进行传递;NSInvocation能传递多个
// 1.通过target和action生成方法前方法签名
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
// 2.通过方法签名生成NSInvaocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
// 3.设置参数位置在第3个位置,有默认值两个参数 self _cmd
[invocation setArgument:¶ms atIndex:2];
// 4.设置action
[invocation setSelector:action];
// 5.设置target
[invocation setTarget:target];
// 6.开始调用
[invocation invoke];
id result;
// 7.获得方法的返回值
[invocation getReturnValue:&result];
消息转发流程
- 第一步动态解析,当前类中如果没有实现这个方法时,调用这个方法就会首先走这个方法,我们可以在这里做容错处理,动态添加一个方法进行,这样就不会crash,第一步成功也就不会再走后面的转发流程了
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(requestOne1)) {
IMP sayOIMP = class_getMethodImplementation(self, @selector(requestOne));
Method sayOMethod = class_getClassMethod(self, @selector(requestOne));
const char *sayOType = method_getTypeEncoding(sayOMethod);
return class_addMethod(self, sel, sayOIMP, sayOType);
}
return [super resolveClassMethod:sel];
}
- (void)requestOne {
}
- 第二步快速转发,当第一步没找到方法时,就会接着调用这个方法,这个时候我们可以动态返回一个包含相同方法的对象,也就是把消息转发给另一个对象,让他代替当前类去完成方法实现,不管是不是我们自己方法接受者实现的,只要有这个方法的imp, 程序就不会崩溃,同理如果成功就不会走后面的慢速转发了
- (id)forwardingTargetForSelector:(SEL)aSelector {//快速转发
if ([NSStringFromSelector(aSelector) isEqualToString:@"requestOne"]) {
return [ViewController2 alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
- 第三步慢速转发,我们可以手动返回一个签名,这样就会继续走后面的forwardInvocation方法,2个方法搭配使用的,NSInvocation进行方法调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(requestOne)) { // v @ :
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[ViewController2 alloc] respondsToSelector:aSelector]) {
[anInvocation invokeWithTarget:[ViewController2 alloc]];
} else {
[super forwardInvocation:anInvocation];
}
}
- 一般我们在hook的时候在第三步做处理,如果找不到实现的方法必然会走到这一步,在线上避免crash就好了,大概率也不会出现这种情况以防万一
- (NSMethodSignature *)hook_methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature* signature = [self hook_methodSignatureForSelector:aSelector];
if (signature) {
return signature;
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)hook_forwardInvocation:(NSInvocation *)anInvocation {
NSString *message = [NSString
stringWithFormat:@"Unrecognized instance class:%@ and selector:%@", NSStringFromClass(self.class), NSStringFromSelector(anInvocation.selector)];
NSLog(@"%@",message);
}
runtime实现模型和字典互转
- 字典转模型,利用runtime的消息发送objc_msgSend,给每一个属性发送set消息赋值
- 模型转字典,也是发消息,给每一个属性发送get消息得到value
-
注意编译objc_msgSend会报错,之前的xocde版本都是要在BuildSetting里面设置objc_msgSend那行==NO,后面发现设置了也没用
image.png - 函数是无返回参数的函数,需要把它强制转化类型
((id (*)(id _Nullable, SEL _Nonnull,
id))(void *)objc_msgSend)(self, selector, value);
#import "DemoModel.h"
#import <objc/message.h>
#import <objc/runtime.h>
@implementation DemoModel
- (instancetype)dictionaryToModel:(NSDictionary *)dictionary {
NSArray *keyArr = [dictionary allKeys];
for (int i = 0; i < keyArr.count; i++) {
NSString *key = keyArr[I];
id value = [dictionary valueForKey:key];
//key首字母大写
NSString *setName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
//生成set方法
SEL selector = NSSelectorFromString(setName);
if ([self respondsToSelector:selector]) {
((id (*)(id _Nullable, SEL _Nonnull,
id))(void *)objc_msgSend)(self, selector, value);
//objc_msgSend函数是无返回参数的函数,需要把它强制转化类型
} else {
NSLog(@"生成%@set方法失败", key.capitalizedString);
}
}
return self;
}
- (NSDictionary *)modelToDictionary {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned int count = 0;
objc_property_t *propertys = class_copyPropertyList(self.class, &count);
for (int i = 0; i < count; i++) {
objc_property_t property = propertys[I];
const char *name = property_getName(property);
NSString *properyName = [NSString stringWithUTF8String:name];
//生成get方法
SEL selector = NSSelectorFromString(properyName);
if ([self respondsToSelector:selector]) {
id value = ((id (*)(id _Nullable, SEL _Nonnull))(void *)objc_msgSend)(self, selector);
[dict setValue:value forKey:properyName];
} else {
NSLog(@"没找到该属性%@",properyName);
}
}
free(propertys);
return dict;
}
@end