CTMediator理解

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:&params 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359

推荐阅读更多精彩内容