美文网首页v2panda的技术专题iOS猛码计划iOS Crazies
iOS组件化实践方案-LDBusMediator炼就

iOS组件化实践方案-LDBusMediator炼就

作者: 淡淡如水舟 | 来源:发表于2016-05-14 15:33 被阅读16515次

    一、中小型App为什么要组件化

    当项目App处于起步阶段、各个需求模块趋于成熟稳定的过程中,组件化也许并没有那么迫切,甚至考虑组件化的架构可能会影响开发效率和需求迭代。而当项目迭代到一定时期之后,便会出现一些相对独立的业务功能模块,而团队的规模也会随着项目迭代逐渐增长,这便是中小型应用考虑组件化的时机了。

    为了更好的分工协作,团队会安排团队成员各自维护一个相对独立的业务组件。这个时候我们引入组件化方案,一是为了解除组件之间相互引用的代码硬依赖,二是为了规范组件之间的通信接口; 让各个组件对外都提供一个黑盒服务,而组件工程本身可以独立开发测试,减少沟通和维护成本,提高效率。

    进一步发展,当团队涉及到转型或者有了新的立项之后,一个团队会开始维护多个项目App,而多个项目App的需求模块往往存在一定的交叉,而这个时候组件化给我们的帮助会更大,我只需要将之前的多个业务组件模块在新的主App中进行组装即可快速迭代出下一个全新App。

    二、如何开始组件化工作

    2.1 组件化的架构目标

    在详细说如何具体开始组件化工作之前,我们对于组件化的期望应该是这样的,一个团队维护一到两个独立App,每个独立App除开包含一些产品相关的非独立模块集之外,还需要用一些独立的业务组件进行组装。 而不管是产品的非独立模块集、还是独立业务组件都需要底层公共库和基础库的支持。如下图所示:

    组件化目标图.png

    2.2 组件化第一步-剥离公共库和产品基础库

    在具体的项目开发过程中,我们使用cocoapod的组件依赖管理利器已经开始从Github上引入了一些第三方开源的基础库,比如说AFNetworking、SDWebImage、SVProgressHUD、ZipArchive等。除开这些第三方开源基础库之外,我们还需要做的事情就是将一些基础组件从主工程剥离出来,形成产品自己的私有基础库仓库,为我们进行业务独立组件的分离做准备。

    这部分我将其分为两类:一类是公共基础库,用于跨产品使用;一类是产品基础库,在某个产品中强相关依赖使用。这里以我们自己产品划分为例,概述一下这两类库都包括哪些基础组件:

    公共库包括:组件化中间件网络诊断第三方SDK管理封装、长连接相关、Patch相关、网络和页面监控相关、用户行为统计库、第三方分享库、JSBridge相关、关于Device+file+crypt+http的基础方法等。

    产品基础库包括:通用的WebViewContainer组件(封装了JSBridge)、自定义数字键盘、表情键盘、自定义下拉列表、循环滚动页面、AFNeworking封装库(对上层业务隐藏AF的直接引用)、以及其他自定义的UI基础组件库。

    2.2 组件化第二步-独立业务模块单独成库

    在基础库成体系的基础上,我们就可以开始按照需求定性将一些相对独立的业务模块独立成库,单独在一个工程上进行开发、测试。

    往往在这个阶段有一个误区,千万不能为了组件化而强行将一些耦合严重的业务模块分出。如果在拆分过程中,拆分模块跟其他模块耦合太严重,那就先放弃这部分模块的独立,毕竟产品是不会单独拿出时间给你做组件化的。

    另外拆分的粒度需要大一点,需要在功能模块的基础上,将业务独立性考虑进去,如果没有就不拆,等以后有了相对独立的模块之后再拆。

    2.3 组件化第三步-对外服务接口最小化

    组件化不是一蹴而就的,我们在完成第二步的时候并不要强行要求去掉组件之间代码的硬依赖,只需要保证单独拆分出来的工程可以独立运行和测试,并且能够通过引用保证其他业务组件和主工程的依赖使用即可。

    当第二步完成之后,我们可以在此基础上总结其他组件和主工程的需求调用,根据需求总结和抽象出当前业务组件对外服务的最小化接口以及页面跳转调用。经过多次总结,我们可以发现组件之间的通信需求无外乎三个方面:URL导航+服务接口调用+消息变量定义。如下图所示:

    组件通信需求.png

    在这个阶段,我们大多数应用会选择JLRoute(蘑菇街的MGJRoute方案也类似)去做URL导航的需求,会通过OpenServiceImpl + Protocol的方案(将所有对外服务提供的接口都在OpenServiceImpl中实现)去做组件间的服务调用,消息变量的声明可以放到对外服务接口的Protocol定义中。

    到了这个阶段,我们的业务组件也已经相对独立,JLRoute能够去掉页面引用的头头文件依赖。OpenServiceImpl+Protocol也将我们最小化的对外服务接口约束到Protocol接口文件中。 如果对于项目组件化要求不高的话,到这一步就可以了。

    三、彻底组件化-LDBusMediator炼就

    3.1 组件化方案不彻底之处和JLRoute的缺陷

    通过第二部分的讲述,我们的组件化工作差不多完成了80%,但是我们依然发现,组件化并不够彻底。

    先来看服务调用方面,我们需要对外提供OpenServiceImpl的头文件,外部模块仍然保持着对业务组件的强依赖,OpenServiceImpl的不兼容变化必然导致所有调用部分的更改,我们期望的黑盒服务便无法实现。如果所有类别的服务接口都在OpenServiceImpl中实现,OpenServiceImpl中的代码会越来越多,难以维护和管理。 另外Protocol文件和OpenServiceImpl的头文件都需要对外披露,如果放到组件实现中,两个组件相互之间有调用,就会导致Podspec的相互循环依赖。

    再看URL导航方面,在我们的项目中,我们在ViewController的类别中通过load方法注册URL-Block,这样能够解决JLRoute的中心化注册问题,但是JLRoute仍然存在其他一些缺陷。JLRoute去中心化的具体使用方式如下:

    + (void)load
    {
        @autoreleasepool {
            [JLRoutes addRoute:@"/xxxx" handler:^BOOL(NSDictionary *parameters) {
                
                UIViewController *baseViewController = parameters[kLDRouteViewControllerKey];
                if (!baseViewController) {
                    baseViewController = [UIViewController topmostViewController];
                }
                if (!baseViewController) {
                    return YES;
                }
                
                XXXXViewController *viewController = [[XXXXViewController alloc] init];
                
                if ([baseViewController isKindOfClass:[UINavigationController class]]) {
                    [(UINavigationController*)baseViewController pushViewController:viewController animated:YES];
                }else if (baseViewController.navigationController) {
                    [baseViewController.navigationController pushViewController:viewController animated:YES];
                } else {
                    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
                    [baseViewController presentViewController:navController animated:YES completion:NULL];
                }
                return YES;
            }];
        }
    }
    

    如上所用,JLRoute的缺陷如下:

    • url短链分布式注册时,导航代码的重复拷贝;
    • 无法通过URL返回一个controller实例;(TabController也就无法从独立业务组件中不引用Controller头文件获取Controller实例完成设置)
    • class的load方法完成注册,太多对启动时Main线程有影响;
    • 同一个url短链的导航方式单一固定,依赖注册
    • 单一业务组件中可导航URL分散,无法统一查看;
    • Debug阶段url传递参数错误、not found没有提示;

    3.2 LDBusMediator总体方案

    针对组件化不彻底的实际问题,结合之前手淘分享的总线架构以及蘑菇街的组件化分享博客,我们完成了一个通用的LDBusMediator中间件帮助我们彻底完成组件化。

    LDBusMediator开源Git地址:

    我们先来看总体的组件化方案:所有的业务组件通过Connector连接到总线中,Connector需要遵循Connector Protocol方可接入。Connector协议规定了URL导航接入和服务接入的协议,Connector通过Class的Load方法将自己的实例注册到中间件的Cache数组中,方便其他组件在调用时中间件可以通过服务发现的方式进行URL导航和服务调用。(具体见如下的图示)

    
    
    @implementation Connector_A
    
    #pragma mark - register connector
    
    /**
     * 每个组件的实现必须自己通过load完成挂载;
     * load只需要在挂载connector的时候完成当前connecotor的初始化,挂载量、挂载消耗、挂载所耗内存都在可控范围内;
     */
    +(void)load{
        @autoreleasepool{
            [LDBusMediator registerConnector:[self sharedConnector]];
        }
    }
    @end
    

    3.3 LDBusMediator-URL导航方案

    URL导航的总线中间件方案很简单,只需要在Connector中实现URL导航接入的接口即可,如图所示:

    LDBusMediator-URL导航.png

    具体使用如下:

    @protocol LDBusConnectorPrt <NSObject>
    
    -(BOOL)canOpenURL:(nonnull NSURL *)URL;
    
    - (nullable UIViewController *)connectToOpenURL:(nonnull NSURL *)URL params:(nullable NSDictionary *)params;
    
    @end
    
    @implementation Connector_A
    
    #pragma mark - LDBusConnectorPrt 
    
    /**
     * (1)当调用方需要通过判断URL是否可导航显示界面的时候,告诉调用方该组件实现是否可导航URL;可导航,返回YES,否则返回NO;
     * (2)这个方法跟connectToOpenURL:params配套实现;如果不实现,则调用方无法判断某个URL是否可导航;
     */
    -(BOOL)canOpenURL:(nonnull NSURL *)URL{
        if ([URL.host isEqualToString:@"ADetail"]) {
            return YES;
        }
    
        return NO;
    }
    @end
    /**
     * (1)通过connector向busMediator挂载可导航的URL,具体解析URL的host还是path,由connector自行决定;
     * (2)如果URL在本业务组件可导航,则从params获取参数,实例化对应的viewController进行返回;如果参数错误,则返回一个错误提示的[UIViewController paramsError]; 如果不需要中间件进行present展示,则返回一个[UIViewController notURLController],表示当前可处理;如果无法处理,返回nil,交由其他组件处理;
     * (3)需要在connector中对参数进行验证,不同的参数调用生成不同的ViewController实例;也可以通过参数决定是否自行展示,如果自行展示,则用户定义的展示方式无效;
     * (4)如果挂接的url较多,这里的代码比较长,可以将处理方法分发到当前connector的category中;
     */
    - (nullable UIViewController *)connectToOpenURL:(nonnull NSURL *)URL params:(nullable NSDictionary *)params{
        //处理scheme://ADetail的方式
        // tip: url较少的时候可以通过if-else去处理,如果url较多,可以自己维护一个url和ViewController的map,加快遍历查找,生成viewController;
        if ([URL.host isEqualToString:@"ADetail"]) {
            DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
            if (params[@"key"] != nil) {
                viewController.valueLabel.text = params[@"key"];
            } else if(params[@"image"]) {
                id imageObj = params[@"image"];
                if (imageObj && [imageObj isKindOfClass:[UIImage class]]) {
                    viewController.valueLabel.text = @"this is image";
                    viewController.imageView.image = params[@"image"];
                    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
                    return [UIViewController notURLController];
                } else {
                    viewController.valueLabel.text = @"no image";
                    viewController.imageView.image = [UIImage imageNamed:@"noImage"];
                    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
                    return [UIViewController notURLController];
                }
            } else {
                // nothing to do
            }
            return viewController;
        }
    
    
        else {
            // nothing to to
        }
    
        return nil;
    }
    
    

    通过LDBusMediator的URL导航方案,有效的解决了前文提出的JLRoute的缺陷:

    1. url短链分布式注册时,导航代码的重复拷贝
    • LDBusNavigator+PresentMode:将通用的导航方式即成到LDBusNavigator中,而无需每个URL注册时重复拷贝。
    1. 无法通过URL返回一个controller实例;(TabController)
    • *URL-Block —> URL-ViewController实例:将之前JLRoute的url-block方式改成了url-ViewController方式,即可满足。
    1. class的load方法完成注册,太多对启动时Main线程有影响;
    • 服务发现的方式,只在load时注册Connector实例:中间件只对每个业务组件的connector实例进行注册,相比URL注册量大量减少load使用。
    1. 同一个url短链的导航方式单一固定,依赖注册
    • 调用时指定Present、Push、Share方式:之前JLRoute只能在注册时候决定导航方式,通过LDBusMediator如何导航显示由调用方决定,默认是Push;Share方式是指pop到导航层次中已经存在的viewController处。
    1. 单一业务组件中可导航URL分散,无法统一查看;
    • 单一组件的connector中集中管理所有可导航URL
    1. Debug阶段url传递参数错误、not found没有提示;
    • Debug阶段的错误Controller提示、包括参数错误、notFound、notSupportController:如果参数错误、notfound无法生成一个viewController实例,中间件在debug阶段会提示。如果URL不支持返回一个Controller,同样会给与提示。

    3.4 LDBusMediator-服务调用方案

    为了更好的通过中间件支撑组件间的服务调用方案,我们在组件实现和中间件之间增加了一层协议接口层。 每个业务组件将自己对外提供的服务接口抽象到一个统一的业务组件协议集合中。 业务组件的实现依赖自己的对外服务接口集并进行接口的实现。

    每个业务组件中的协议部分有两种:一种是服务协议,其他组件可以通过Mediator拿到对外开放的服务实例调用服务接口;一种是Model协议,服务协议中的接口可以给其他组件一个协议化对象,其他组件也可以组装一个协议化对象通过参数传入。

    为了方便业务组件实现和协议集合的版本对应,需要保证协议集合的大版本(如x.y)和业务组件的大版本(如x.y.z)中的x保持一致;协议集合中一般没有补丁版本的迭代,当其他业务组件调用需要增加接口进行兼容版本升级(y+1),减少或者修改接口则需要协议集合和业务组件中的x同时+1(x+1); 如果自身业务组件升级不能影响对外协议接口的调用,升级版本主要为补丁版本迭代(z+1)或 兼容版本升级(y+1);

    组件协议集合 单独通过一个Git地址进行管理,单独配置podspec,单独通过协议的版本仓库进行管理;所有的协议集合的git统一放到Git的一个组中进行管理。

    具体方案如下:

    LDBusMediator-服务调用.png
    @protocol LDBusConnectorPrt <NSObject>
    /**
     * 业务模块挂接中间件,注册自己提供的service,实现服务接口的调用;
     * 
     * 通过protocol协议找到组件中对应的服务实现,生成一个服务单例;
     * 传递给调用者进行protocol接口中属性和方法的调用;
     */
    - (nullable id)connectToHandleProtocol:(nonnull Protocol *)servicePrt;  
    
    @end
    
    
    @implementation Connector_A
    /**
     * (1)通过connector向BusMediator挂接可处理的Protocol,根据Protocol获取当前组件中可处理protocol的服务实例;
     *  (2)具体服务协议的实现可放到其他类实现文件中,只需要在当前connetor中引用,返回一个服务实例即可;
     *  (3)如果不能处理,返回一个nil;
     */
    - (nullable id)connectToHandleProtocol:(nonnull Protocol *)servicePrt{
        if (servicePrt == @protocol(ModuleAXXXServicePrt)) {
            return [[self class] sharedConnector];
        }
        return nil;
    }
    @end
    
    

    LDBusMediator中间件的服务调用方案的优势:

    1. 通过中间件支撑,不暴露任何实现文件的头文件;
    • 组件对外提供的服务通过最小化抽象的“协议接口集”披露;
    • 组件的实现Pod不暴露任何头文件;
    1. 每个业务组件提供黑盒服务
    • 调用者不用关心具体实现细节;
    • 业务组件的实现升级、或者更换(包括整个业务组件更换)不影响调用者的调用修改;
    1. 为业务组件Framework化、自动化构建奠定基础

    相关文章

      网友评论

      • 月半的瘦子:很想大神开个课堂,讲解下
      • 愤怒的小懒懒:很想大神开个课堂,讲解下
      • 愤怒的小懒懒:看得有点懵,不明觉厉,大神,您这个用的算是哪个方案的组件化,听说有,协议?url注册??还是CTMetiator。
      • 破弓:您好,看了您的文章对组件化的认识又进了一层.但"服务调用"这边还是有些疑问!

        LoginServiceImpl内部注册类与协议:
        + (void)load{
        [[ProtocolMediator sharedCacheManager] addClass:self protocol:@protocol(LoginServiceProtocol)];
        }

        调用:
        Class anyClass = [[ProtocolMediator sharedCacheManager] classForProtocol:@protocol(LoginServiceProtocol)];
        id <LoginServiceProtocol> anyObj = [[anyClass alloc] init];
        NSLog(@"zc see %@",[anyObj getUserName]);

        向外也只暴露了LoginServiceProtocol,并不用暴露LoginServiceImpl啊?
      • Karos_凯:你这个和mgj的router方案,区别不大,本质也是在自己的组件里完成注册,并提供服务。

        进步点:
        比mgj进步的是 protocol 中心化管理。

        疑惑点:
        canRouteURL, 是否有必要有这个方法呢?
        关于导航,ld 是在组件的挂载点做的,为什么会考虑在里面做,如果说我把navagator 作为一个工具类,效果上有不是也可以吗?
      • 火郎君:demo里serviceForProtocol写成registerServiceByProtocol比较好(虽然可以在其他服务类中实现->由其他服务对像提供)
      • 阿凡提说AI:感觉写的很好,就是看不懂,能不能录个视频,或者更详细点讲。
      • 谢谢生活:把跳转url写到- (nullable UIViewController *)connectToOpenURL:(nonnull NSURL *)URL params:(nullable NSDictionary *)params 非常不科学 代码量感觉会非常冗余,而且不好维护
      • e2b9406007be:使用cocoapods本地库导入的模块,+load方法并不会自动执行,私有库还没试过,望指教
      • slimsallen:感觉还不是很理解
      • 417d882aca0b:通过Mediator 拿到模块的服务protocol后,打开URL, 模块把对应的controller实例化,返回给Mediator, Mediator 让navigator push or present,share. 我有2个问题: 1.感觉hook那里有点绕,既然让LDNavigator负责导航,为啥又来个拦截?2.是否想让app里所有的controller 导航都让LDNavigator包干?模块内部2个相邻的页面间跳转,可以[self.navigationcontroller push]?
      • hopestar90:您好,有个问题想和您讨论一下,@synchronized(g_connectorMap) {
        if (g_connectorMap == nil){
        g_connectorMap = [[NSMutableDictionary alloc] initWithCapacity:5];
        }

        NSString *connectorClsStr = NSStringFromClass([connector class]);
        if ([g_connectorMap objectForKey:connectorClsStr] == nil) {
        [g_connectorMap setObject:connector forKey:connectorClsStr];
        }
        }
        这段代码,如果g_connectorMap为空会使你的同步代码失效吧
      • hopestar90:辛苦啦~针对 4.同一个url短链的导航方式单一固定,依赖注册这一点,跳转方式其实也可以在调用时指定吧
      • 海浪萌物:connectToOpenURL:里面判断太多了吧,感觉这是蘑菇街的URL注册方案和protocol注册方案结合起来的,少了些注册url的量而已
      • 鱼子酱zi:感谢大神给出一种切实可行的方案,来实施组件化。 :+1:
      • 踏浪帅:哈文章不错啊 最近也在搞模块化 有个问题 就是关于如何处理回调不知道你们是怎么解决,比如模块A的一个页面 跳转模块B的一个页面, 在从B返回到A时有些参数要回调回去,不知道你们是怎么做到,目前我想到的是用通知的方式;不知道有没有其它更好的方式?
      • 赵凡之:非常不错,正在做这方面的工作,参考参考。组件化是一个系统的工程,要求规范化,标准化,有了一套体系,以后开发工作将会非常便利,团队及个人效率会得到很大提升。
      • f7e35e82365e:您好,我拜读了您的demo,有一些地方不是很明白,我创建的试图控制器给个URL,为什么跳转不过去
      • cfe577244994:楼主,您好,我想问一下, 针对于比如有回调的情况, 怎么进行优雅的处理?
      • 618ecb324f60://判断某个URL能否导航
        +(BOOL)canRouteURL:(nonnull NSURL *)URL;

        //通过URL直接完成页面跳转
        +(BOOL)routeURL:(nonnull NSURL *)URL;
        +(BOOL)routeURL:(nonnull NSURL *)URL withParameters:(nullable NSDictionary *)params;

        //通过URL获取viewController实例
        +(nullable UIViewController *)viewControllerForURL:(nonnull NSURL *)URL;
        +(nullable UIViewController *)viewControllerForURL:(nonnull NSURL *)URL withParameters:(nullable NSDictionary *)params;
        个人感觉这些方法完全可以合并成一个routeURL,貌似CTMediator方案比较精简,条条框框少,调用也比较明确,传参返回就这么简单,Connector的内存常驻也一并省掉了~
        可能我认知有限,博主别跟我一般见识,啃几个月再过来看看~~ :smile:
      • 618ecb324f60:赞赞赞,没太看懂跟蘑菇街的方案哪里不同,回头看下demo,再次谢谢博主的分享:smile:
      • F森:学习了!有个问题,这种组件化方式面对反向传值问题该如何解决呢?比如在我的页面,点击头像跳转到登陆模块,登陆完后如何刷新我的页面,将头像等信息刷出来呢?有几种实现手段?谢谢😀
        618ecb324f60:@F森 注册一个url,让登陆组建完成登陆后调用就好了
      • 590bf6c23733:你这个还是蘑菇街的URL注册方案和protocol注册方案,本质上一模一样,名字换成BUS而已。

        不同的地方只是你的方案是蘑菇街的升级版,在上层添加了额外功能,底层并无本质区别。

        关于CTMediator的方案似乎你并没有理解透彻,你说的缺陷里面有一部分其实是你没理解透彻而认为的缺陷,另外一部分是组件化方案会遇到的普遍问题,基本无解。

        如果你继续改造下去,你会发现最终你还是会用成CTMediator方案的。
      • 南栀倾寒:感觉文本有待提高,本来很简单的东西,描述的很罗嗦很模糊,很拗口。
        淡淡如水舟:@南栀倾寒 谢谢指点,我会努力的!
      • 蛊啸山庄:以前开发很少整理东西,一个新项目开始又是从头开始写,重复编码降低开发效率。现在想用组件化开发区敦促自己整理和梳理业务逻辑应该是可行的吧?
      • godgnay:您好,拜读了这篇文章之后,感觉的很有想法,对于demo有一个疑问。如果我想在外部启动这个app的时候,第一次就直接选定在tabController中的第二个上应该怎么处理?想通过URL的设置实现您setURLHookRouteBlock这一段的内容。
        淡淡如水舟:@shengyang_yu 可以在setURLHookRouteBlock中去做:通过block中的controller参数判断是否需要处理,如果要,就获得当前window的rootTabController,设置selectIndex=2,并通过[[LDBusNavigator navigator] showURLController:tabController baseViewController:baseViewController routeMode:NavigationModeShare];显示RootTabController;
      • 万八量化:你好 看了本文之后 我对比了自己的实现方式
        情况是这样的 我司app体量较小
        所以我维护了一个urlList 类似这样:
        static NSString *const customVC = @"JKViewController";
        然后URLRoute类方法是这样:
        + (void)openUrlWith:(NSString *)url parameters:(NSDictionary *)parameters;
        这个类的实现如下:
        判断url是否 hasPrefix "http" 如果是 就打开webVC 然后return
        将url实例化成类 -> 利用runtime拿到ivar列表 ->赋值 -> 跳转

        使用方式
        [URLRoute customVC parameters:@{ }];

        实现了去头文件化
        这样做对吗 :flushed:
        淡淡如水舟:@JK77 其实DLBusMediator没有组件化也可以用,就是把这个App当做一个Host组件;所以你可以直接用我这个开源组件就可以了;另外BusMediator中对于Pop、Push、Share可以在parameters中传这个导航方式的key进来就可以了;
        万八量化:@philon 首先谢谢你的回复 :smile: (1) 我意识到了这个问题 如果文件数量太多, 很是麻烦;
        所以修改为在load中向URLRoute注册url, URLRoute维护一个Dict,用来处理url和class的映射关系

        (2) 我觉得一个个参数赋值比较麻烦 :flushed: 所以...
        (3) 我可以修改下 openUrlWith:parameters 加一个options的Enum参数(.pop,.push.share)
        如果是share就返回组件实例 由调用者做处理
        不知道这样修改会不会好点 :flushed:
        ps 还没有做组件化 因为公司非常小 app也很小 我只是在想这方面的问题 目前只改了一个类 感觉还挺方便 受制于眼界所限 还请赐教 感激不尽
        淡淡如水舟:@JK77 个人觉得这样不太好;(1)不建议通过中心化注册方式(urlList)去管理可导航的URL,如果app迭代扩大之后,必然会进行组件化,中心化的url管理方式是比较大的问题;(2)不建议用runtime去实例controller和拿ivar列表,controller如何初始化,是否返回单例,controller的property如何赋值应该交给各个独立的模块;而不是中心化去做;(3)openUrlWith:parameters这个方法仍然只能做跳转支持,如果只想通过url拿到controller实例去给tabController赋值还是做不到;

        最后建议:如果app体量比较小,组件化也还没有做的话,可以先用JLRoute这个开源库;在需要导航的Controller的类别的load方法注册url和handler去解除中心化;这样应该就能满足你的需求了;
      • Colorless:好赞 :fist:
      • bigParis:您好, 拜读了您的源码, 有几点不理解的地方, 还请不吝赐教. 请问:ModuleAXXXServicePrt.h 里面定义了获取和设置某model的代理moduleA_getItemWithName, moduleA_deliveAprotocolModel 但是模块A不可能只有ModuleAXXXItem这一种model吧? 那岂不是要定义N多这种代理去让module间能够通过这种方式进行数据传递, 是不是我理解的有问题?
        淡淡如水舟:@bigParis 是的,在应用中最常见的情况就是登录模块,其它业务组件基本上都需要登录信息,这个时候可以考虑用modelItemPrt来进行协议化; 我们这样做的目的就是不像暴露任何实现文件的头文件;
        bigParis:@philon 感谢您的回复, 大概明白了, 如果2个module之间真的有很多数据要传递, 这种module是不适合拆分为单独的组件的, 组件化的应该是那种其它组件对其依赖本身就很少, 所以组件见的通信本身就是应该只携带极少量的数据的, 可以这样理解? 但是这样一来, 假设有个common组件, 可能很多module要通过这个组件拿数据, 那就必然会导致有很多类似`-(nonnull id<ModuleAXXXItemPrt>)moduleA_getItemWithName:(nonnull NSString *)name
        age:(int)age;`这样的代理, 而且使用的时候应该是先通过类似字典的东西(name, age)取到ModuleAXXXItemPrt, 然后再用ModuleAXXXItemPrt去调相应的方法, 给人的感觉是, 通过去model化来达到module间的解耦.
        淡淡如水舟:@bigParis 你好,首先谢谢你的关注;首先我们在组件化过程中是要尽量避免组件之间协议化对象之间的传递,能用简单数据交互就尽量用简单数据交互;所以你说的这种N中代理方法我们应该尽量的减少; 但是在实际项目环境中我们不想把一个对象的数据通过NSDictionary拆分传递给另外一个组件,或者需要传递一个比较多的数据时候,这个时候我们倾向于用ModelItemPrt去封装;
      • tony关东升:写的很细,很认真。我来说两句:组件划分粒度还是太粗,组件粒度大小和边界是非常难课题,很对架构解决就是这个问题,在我看来应该基于分层架构下组件化,即组件可以分为表示层组件、业务层组件、持久层组件。
        淡淡如水舟:@tony关东升 完全脱离业务层的数据持久化模块,我觉得这种情况应该是很少的;我们现在的应用中,像你说的这种情况也就是登录模块,登录的session数据可能其他业务模块都需要用到;这种情况下我们是单独把登录模块独立出来作为一个业务组件的;如果是业务访问的数据,在app的应用领域内,是很难把表示层和业务逻辑层单独分成不同的组件,我觉得这是设计模式的问题,就跟大家现在讨论的Flux、MVVM、MVP这些要去考虑的问题;
        tony关东升:@philon 如果你的业务组中有访问数据(本地或云)应该如何设计呢?在我看来这种情况下访问数据应该从业务组件中分离出来,做成新的组件,这就是数据持久化组件,这种组件是与业务无关的。其实这就是分层架构设计,一个应用应该分为:表示层、业务逻辑层、数据持久层和信息系统层。
        淡淡如水舟:@tony关东升 这就看我们组件化的需求是什么,需求决定边界。我这篇文章主要是针对项目中相对比较独立的业务模块进行代码硬隔离,也就是手淘之前分享的bundle架构提到的粒度,至于每个bundle采取什么设计模式去开发,不去做这个约束,给业务方足够的自由。另外粒度越小,需要约束的东西越多,反而会影响我们的开发效率。另外跟项目大小有关,如果业务独立性不够,那就不要分了,组件化能够满足项目需求就可以了,过多的划分和设计都很难带来明显的价值!这是我的理解,赐教!
      • 31def47dacdc:看着不错
      • Dean麦兜:和我们的插件化思路差不多,写的很详细,不过你这没讲A与B插件数据交互和回传,更多的看到的是router,而不是service,真正实现插件化和组件化,router和service缺一不可
        花生_sniper:@Dean麦兜 @philon 关于service有没有推荐的文章拜读一下?谢谢
        淡淡如水舟:@Dean麦兜 确实,router和service缺一不可,文章关于service介绍得比较少,不过开源的那个中间件是支持service的,通过协议接口层规范组件,外部调用通过中间件获得服务实例对象后调用协议接口方法即可。
      • 玻璃上的小强:666 赞一个
      • 1d1146c7943d:赞一个
      • 心中的信念:希望写个例子,谢谢
        yuditxj:@philon 那组件内部的跳转怎么办?
        淡淡如水舟:@心中的信念 在那个开源的代码中有例子~~可以去看看
      • 奇哥Dodge:写的非常好,受用
      • ChangeWorld:666 就喜欢这样的 雪中送炭 我们项目刚好要重构成组件化~ 统一跳转接口。。。我先看看先,好用就用这个啦
        淡淡如水舟:@ChangeWorld 恩,有疑问的地方随时咨询我!~
      • Ryan文濤:组件化确实值得一用,毕竟APP一大起来,团队开发的话,组件化可以解决很多不必要的冲突。
        Ryan文濤:@philon 是啊,这也是一种技术的提高。可惜我还没有达到那种技术,:fearful:
        淡淡如水舟:@Ryan文濤 除开对项目有益之外,对于开发人员做需求开发的时候设计代码层次,提高他们的代码质量和编码能力都比较有用!
      • 84a6eed103c0:膜拜大神
        淡淡如水舟:@大娱乐家Q 谢谢支持~~😊😊

      本文标题:iOS组件化实践方案-LDBusMediator炼就

      本文链接:https://www.haomeiwen.com/subject/avmmrttx.html