iOS 架构项目优化

iOS中CTMediator原理探究

2020-10-27  本文已影响0人  傲骨天成科技

一、前言

相信各位iOS开发者在做组件化开发中都有考虑过组件之间的通讯问题。由于市面上有主流开源的组件化通讯有蘑菇街的MGJRouter、CTMediator等

其中MGJRouter的问题
1.每一个组件都需要初始化,内存中需要保存一份跳转的url表,这样当项目足够大,组件足够多的时候对内存会是一种负担。

2.参数的格式有限制,不能传递模型等问题

因此,我选了CTMediator进行使用。下来我们来看看CTMediator的主要原理是什么。

二、CTMediator的结构

CTMediator按照功能结构,我们可以将CTMediator分为三个部分。

1.CTMediator类:承担大总管的职务,居中调度。
2.Targer_(ModuleName)类:括号里面的名字可以自定义。承担组件对外暴露接口功能,组件需要提供怎样的服务。
3.CTMediator+(ModuleName)分类:括号里面的名字可以自定义。里面声明了组件可以调用的接口。

三、CTMediator核心功能

CTMediator主要采用target-action的方式实现组件间,自身的功能独立,不需要依赖任何组件模块

CTMediator作为一个通讯中枢,主要通过2中方案进行实现:

1.使用cocoa touch 提供的命令模式,将消息和消息接收者封装成一个对象,进行执行。

首先,利用target-action生成方法签名
然后,创建NSInvocation对象,执行invoke. 并拿到返回的结果。
利用方法签名,NSInvocation实现

// 1.通过target和action生成方法前方法签名
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];

// 2.通过方法签名生成NSInvocation(调用的意思)对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];

// 3.设置参数位置在第二个位置
[invocation setArgument:&params atIndex:2];

// 4.设置action
[invocation setSelector:action];

// 5.设置target
[invocation setTarget:target];

// 6.开始调用
[invocation invoke];


 id result;
// 7.获得方法的返回值
[invocation getReturnValue:&result];
2、或者利用runtime进行反射,将类字符串和方法字符串转换成类和SEL方法选择器
  // 1.生成对应类名的字符串
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    // 2.将类字符串转换成类对象
    Class targetClass = NSClassFromString(targetClassString);
    
    // 3.初始化该类
    id target = [[targetClass alloc] init];
// 1.生成对应类下的方法名字符串
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];

  // 2.根据方法名字符串生成方法选择器
    SEL action = NSSelectorFromString(actionString);

然后通过performSelector方法调用

// 直接调用该类生成对象的对应方法,同时传递参数
  [target performSelector:action withObject:params];

实际上CTMediator是先使用方法签名和NSInvocation进行方法的调用的,当几种返回值不满足的条件下才去执行performSelector调用方法的

CTMediator判断返回值类型,如果是void,NSInteger,BOOL,CGFloat,NSUInteger就使用NSInvocation进行特殊处理,不是的话就直接返回performSelector的返回值类型.

原因是[target performSelector:action withObject:params]返回的是id类型,因为在ARC中是不允许隐士的将void,NSInteger,BOOL,CGFloat,NSUInteger这些类型转换为id类型的。

四、对外提供服务的接口

如果你封装的业务组件中的某些类需要对外提供服务,就需要创建自己的接收动作类
比如CPHomeModule要对外提供服务,那么就要创建一个Target_CPHome类,然后在Target_CPHome类中声明对外服务的接口,并进行实现。
注意:Target_CPHome类在当前业务组件之内的。它属于业务组件的一部分。

例如:

import UIKit

@objc class Target_CPHome: NSObject  {

    /// 获取主页VC
    @objc  func Action_GetHomeVC(_ par:[AnyHashable:Any]?) -> UIViewController {
        
        let homeVC = CPHomeVC.init()
        
        return homeVC
        
    }
    
    /// 获取主页搜索VC
    @objc  func Action_GetHomeSearchVC(_ par:[AnyHashable:Any]?) -> UIViewController {
        
        let vc = CPHomeSearchVC.init()
        
        return vc
        
    }
    
    
}

五、CTMediator+CPHomeModule组件通讯使用的分类

单独创建一个跳转组件例如CPJumpModule,然后在此组件中创建对应业务组件的通讯类,CTMediator的分类,如CTMediator+CPHomeModule,
使用此种做法巧妙的对项目进行了解耦。利用CTMediator的分类可以直接通过实例化CTMediator进行调用相关的方法,获取我们想要的信息,比如跳转的类对象。

import UIKit
import CTMediator

// 模块名,注意,如果模块名改变了(也就是DisplayName),需要改变对应的名称,
let kCTMediatorTarget_CPHomeModule = "CPHomeModule"

/*

比如:获取app的名称
// app名称
func kCTMediatorTarget_CPItemPoolModule() -> String {
    
    let appName:String = Bundle.main.infoDictionary!["CFBundleExecutable"] as! String
    
    return appName
}
*/

// 对应模块的Target_后面的名称
let kCTMediatorTarget_CPHome = "CPHome"

public extension CTMediator {
    
    /// 返回主页VC
    @objc func CTMediator_vcForHomeVC() -> UIViewController? {
        
        // GetHomeVC代表Target_ CPHome类里面Action_后面的名称
        if let viewController:UIViewController = self.performTarget(kCTMediatorTarget_CPHome, action: "GetHomeVC", params:[kCTMediatorParamsKeySwiftTargetModuleName:kCTMediatorTarget_CPHomeModule], shouldCacheTarget: false) as? UIViewController {

            return viewController
        }
        
        return nil
        
    }
    
    /// 返回主页搜索VC
    @objc func CTMediator_vcForHomeSearchVC() -> UIViewController? {
        
        
        if let viewController:UIViewController = self.performTarget(kCTMediatorTarget_CPHome, action: "GetHomeSearchVC", params:[kCTMediatorParamsKeySwiftTargetModuleName:kCTMediatorTarget_CPHomeModule], shouldCacheTarget: false) as? UIViewController {

            return viewController
        }
        
        return nil
        
    }

}

六、CTMediator还提供了远程app调用入口

也就是其他app通过url白名单的方式启动我们的app进行相关的操作

// 远程App调用入口
- (id _Nullable)performActionWithUrl:(NSURL * _Nullable)url completion:(void(^_Nullable)(NSDictionary * _Nullable info))completion;

它的使用方式也就是app间的跳转,例如:A->B A跳转B
1.需要我们给需要被跳转的应用B设置自己的白名单,例如zhengruiceping,
2.iOS13之前在应用B中的AppDelegate中的application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:])方法中进行CTMediator的操作,此方法可以接收到应用A传递过来的URL,通过URL进行判断跳转

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        
        debugPrint("要打开的url0=\(url)")
        if let vc = CTMediator.sharedInstance().performAction(with: url, completion: nil) {
            
            CTMediator.sharedInstance().push(vc as! UIViewController, animated: true)
        }

        
        return true
    }

3.iOS13或之后在应用B中的SceneDelegate中的func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)方法中进行CTMediator的操作,此方法可以接收到应用A传递过来的URL,通过URL进行判断跳转

@available(iOS 13.0, *)
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        
        for item in URLContexts.enumerated() {


            let a:UIOpenURLContext = item.element
            debugPrint(a.url)
            
            let url:URL = a.url
            
            debugPrint("要打开的url3=\(url)")
            if let vc = CTMediator.sharedInstance().performAction(with: url, completion: nil) {
                
                CTMediator.sharedInstance().push(vc as! UIViewController, animated: true)
            }
        }
    }

4.在应用A中写上要跳转的url,通过openURL进行跳转到B应用

NSURL *url = [[NSURL alloc] initWithString:@"zhengruiceping://CPHome/GetHomeMessageVC?kCTMediatorParamsKeySwiftTargetModuleName=CPHomeModule"];
        if ([[UIApplication sharedApplication] canOpenURL:url]) {
            
            
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL success) {
                
                NSLog(@"成功了么==%d",success);
            }];
        }

注意:
url的写法:
zhengruiceping代表的是应用B的白名单
CPHome代表域名,也就是应用B中的Target_CPHome类
GetHomeMessageVC代表路径,也就是应用B中Target_CPHome中的Action_ GetHomeMessageVC方法
kCTMediatorParamsKeySwiftTargetModuleName代表模块名称的key值
CPHomeModule代表对应的模块名称

[[UIApplication sharedApplication] canOpenURL:url]中注意,如果调用了此方法,则需要在应用A中注册应用B的白名单,否则此方法返回NO

七、Swift组件与Objective-C组件互相调用的全部8种情况,及其对策

其实如果你的工程是Swift调用者、Swift响应者的情况,那么应用CTMediator就一点问题都没有。

对于Objective-C的开发团队来说,如果开始渐进地将工程Swift化,那么就需要分各种情况去处理block和closure转化的问题。在这里我把可能出现的所有情况及其处理方法总结如下:

1.Swift调用者 + Extension + Swift响应者
      1.Extension中给到的params需要带有      kCTMediatorParamsKeySwiftTargetModuleNamekey来给到target所在module的名字
      2.Extension中不需要针对closure做任何转化
      3.响应者target-action跟正常情况一样写
      4.响应者action方法首参数不要带Argument Label,用_
2.Swift调用者 + Extension + Objective-C响应者
      1.Extension中给到的params不需要带kCTMediatorParamsKeySwiftTargetModuleNamekey
      2.Extension中需要将closure转化成block对象之后再放入params传过去
      3.响应者的Target-Action跟正常情况一样写
3.Swift调用者 + Category + Swift响应者
1.Category中给到的params需要带有kCTMediatorParamsKeySwiftTargetModuleNamekey来给到target所在module的名字
2.Category中不需要针对block做任何转化
3.响应者的Target-Action需要将block转化成closure
4.响应者action方法首参数不要带Argument Label,用_
4. Swift调用者 + Category + Objective-C响应者
1.Category中给到的params不需要带kCTMediatorParamsKeySwiftTargetModuleNamekey
2.Category中不需要针对block做任何转化
3.响应者的Target-Action跟正常情况一样写
5.Objective-C调用者 + Category + Objective-C响应者
1.Category中给到的params不需要带kCTMediatorParamsKeySwiftTargetModuleNamekey
2.Category中不需要针对block做任何转化
3.响应者的Target-Action跟正常情况一样写

6.Objective-C调用者 + Category + Swift响应者
1.Category中给到的params需要带有kCTMediatorParamsKeySwiftTargetModuleNamekey来给到target所在module的名字
2.Category中不需要针对block做任何转化
3.响应者的Target-Action需要将block转化成closure
4.响应者action方法首参数不要带Argument Label,用_

7.Objective-C调用者 + Extension + Objective-C响应者
1.Extension中给到的params不需要带kCTMediatorParamsKeySwiftTargetModuleNamekey
2.Extension中需要将closure转化成block对象之后再放入params传过去
3.Extension中的方法需要带前缀@objc
4.响应者的Target-Action跟正常情况一样写
8.Objective-C调用者 + Extension + Swift响应者
1.Extension中给到的params需要带有kCTMediatorParamsKeySwiftTargetModuleNamekey来给到target所在module的名字
2.Extension中不需要针对closure做任何转化
3.Extension中的方法需要带前缀@objc
4.响应者target-action跟正常情况一样写
5.响应者action方法首参数不要带Argument Label,用_
上一篇 下一篇

猜你喜欢

热点阅读