iOS 组件化调研

165 阅读4分钟

iOS 的业务组件化(模块化)

iOS 组件化,参考大搜车公司组件化方案用Swift实现的Modular 使用

Swift

static func moduleDescription(description: ModuleDescription) {
     description.moduleName("testSwift")
         .method { method in
             method.name("push")
                   .selector(selector: #selector(push))
         }
         .method { method in
             method.name("present")
                   .selector(selector: #selector(present(dic:)))
         }
         .method { method in
             method.name("log")
                    .selector(selector: #selector(printLog(logString:)))
         }
 }

OC

+ (void)moduleDescriptionWithDescription:(ModuleDescription * _Nonnull)description {
    description.moduleNameClosure(@"testOC")
        .methodClosure(^(ModuleMethod * moduleMethod) {
            [moduleMethod selectorWithSelector: @selector(push:)];
            [moduleMethod name:@"push"];
        })
        .methodClosure(^(ModuleMethod * moduleMethod) {
            [moduleMethod selectorWithSelector: @selector(present:)];
            [moduleMethod name:@"present"];
        });
}

调用

Module.share.invokeWithModuleName("testSwift", selectorName: "log", params: [
                "id": "1",
                "name": "顾钱想",
                "sex": 20
            ], callback: nil)
Module.share.invokeWithUrl("scheme://push/testSwift?code=1111", callback: nil)

1. 如何封装组件

iOS 的组件化手段非常单一,就是利用 Cocoapods 封装成 pod 库,主工程分别引用这些 pod 即可。最终想要达到的理想目标就是主工程就是一个壳工程,其他所有代码都在组件 Pods 里面,主工程的工作就是初始化,加载这些组件的,没有其他任何代码了。

拿弹个车为例

image-20220706220546507

CocoaPods,iOS包管理工具,类似于前端的npm, yarn。其原理:根据Podfile描述,找到对应代码库的podspec文件,然后根据podspec中的描述,找到代码库,并且找到之后,拷贝需要的文件到自己的工程中。具体如果用 Cocoapods 打包一个静态库 .a 或者 framework链接

2. 如何划分组件

iOS 划分组件虽然没有一个很明确的标准,因为每个项目都不同,划分组件的粗粒度也不同,但是依旧有一个划分的原则。

App之间可以重用的 Util、Category、网络层和本地存储 storage 等等这些东西抽成了 Pod 库。还有些一些和业务相关的,也是在各个App之间重用的。

原则就是:要在App之间共享的代码就应该抽成 Pod 库,把它们作为一个个组件。不在 App 间共享的业务线,也应该抽成 Pod,解除它与工程其他的文件耦合性。

常见的划分方法都是从底层开始动手,网络库,数据库存储,加密解密,工具类,地图,基础SDK,APM,风控,埋点……从下往上,到了上层就是各个业务方的组件了,最常见的就类似于购物车,我的钱包,登录,注册等。

3. 组件间的消息传递

划分好了组件,那么我们就要解决两个问题:1.各个页面和组件之间的跳转问题。2.各个组件之间相互调用。

iOS组件化有以下三种方案

  • URLRouter注册
  • Protocol-Class注册
  • Taret-Action

我们公司的方案是Target_Action,但表现上Target_Action可以通过URL来表现,也就是说URL通过解析能解析成Target_Action的形式。

1.url-block

url-block 这个方式我最早是在蘑菇街的组件化方案中看到的,简单来说它就是配置“一段 url 对应一个 block 方法”,当我们使用 openUrl:方法的时候,其实就是在调用这个 block 方法。

比如下面代码:

[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
    NSNumber *id = routerParameters[@"id"];
    // create view controller with id
    // push view controller
}];

这是蘑菇街调用详情页的方法,MGJRouter作为中间件,任何组件间的调用都有它来统一处理。不过这种 url 的传递方式使得参数类型受到了限制,无法传递非常规的参数类型,比如 UIImage。于是就有了下面这种 protocol-class。

2.protocol-class

protocol-class 也是在蘑菇街组件中提出的,对于需要非常规参数的时候,他们就使用这种方案。protocol-class 通过在内部维护了一个 protocol 和 class 的映射表,根据对于的 protocol 来获取对应的 class,使用方不需要在意 class 是哪个类,只要知道它实现了 protocol 就可以。

img 代码如下:

[ModuleManager registerClass:ClassA forProtocol:ProtocolA];
[ModuleManager classForProtocol:ProtocolA];

[ModuleManager classForProtocol:ProtocolA] 的返回结果就是之前在 ModuleManager 内部注册好的 dict 里 protocol 对应的 class。

3.target-action

这种组件化方式是利用一个中间件来调用,中间件利用 runtime 来调用其他组件,这种方式可以做到真正意义上的解耦。然后再通过实现中间件 category 的方式来提供服务,使得使用者只需要依赖中间件,而组件不需要依赖中间件。

img

核心代码总结

[self performTarget:@"A" action:@"viewController" params:params shouldCacheTarget:NO];
//这里的类和方法子都是运行时动态生成的
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    if (targetName == nil || actionName == nil) {
        return nil;
    }
    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);
    .......
}

4.AppDelegate解耦

典型的解决方案有以下几种:

• plist静态注册

• load方法动态注册

• 基于__attribute__的clang语法,把注册信息写到mach-o文件里 美团方案 facebook方案 案例

个人实现的一个组件化小Demo Modular喜欢的给个start

友情链接:

组件化 OR 模块化

iOS 组件化 —— 路由设计思路分析

iOS应用架构谈 组件化方案

App 组件化与业务拆分那些事