为了账号安全,请及时绑定邮箱和手机立即绑定

iOS组件化解决方案

标签:
iOS

由于近期迭代周期变长,有时间想想代码持续改进的问题,再加上各业务模块代码从去年杂乱无章的状态,到目前整体结构基本清晰,进而想到了模块之间解耦的问题,于是有了本文,关于iOS组件化的一些思路及最终的解决方案。

为什么要组件化

技术界如今已存在很多关于组件化的解决方案,Class-Protocol、Target-Action等等,无论采用哪种方案,大家的目的都是为了解决代码庞大到一定规模时,依旧可以比较方便的进行管理和开发。这个时候就需要对各个业务模块进行梳理,在代码层面实现高内聚、低耦合,降低它们相互之间的变化带来的影响,从而提升开发效率。

先来看看如下两个图,对比一下:


模块间跳转现状.jpg

中间层框架解耦方案.jpg

从图中可以看出,在经过中间层框架跳转分发之后,各业务模块之间不存在引用关系,代码相互隔离,调用层次清晰,实现了模块间的真正解耦,完美的过渡到组件化的流程。

框架内部的实现原理是什么

这里采用的是openURL: 和 openWithMapKey:的两种调用方式,以便实现App之间跳转及模块之间的跳转操作,具体采用哪种方式之后会讲到,下面来看一下中间层框架 JCNavigator 简单的调用过程:

中间层跳转分发流程.jpg


接下来详细介绍下流程中出现的方法及相关类:

openURL:

  • 支持App之间的跳转
    支持设置、电话等系统Apps和info.plist白名单中的第三方Apps跳转

  • 支持Module之间的跳转
    传参时仅支持NSString数据类型的赋值

  • 支持Module内部页面的跳转
    传参时仅支持NSString数据类型的赋值

openWithMapKey:

  • 支持Module内部页面的跳转
    传参时支持NSStringNSArrayUIImage等系统数据类型及自定义数据类型的赋值

  • 支持Module之间的跳转
    为了模块间的解耦,传参时建议使用系统数据类型,避免使用自定义数据类型

总结:两种调用方法各有优势,通过推送、Widget、第三方App等外部入口打开你的App执行跳转操作时,建议使用openURL:,其他情况的跳转都采用openWithMapKey:的调用方式。

JCModuleMap

从上图可以看出,模块间无论是通过 openURL: 还是 openWithMapKey: 方法,都是找到对应的JCModuleMap,然后实现最终的页面跳转。接下来看看如何通过JCModuleMap实现这一操作:

  • 子类化


    JCModuleMap子类化.jpg

//  JCTestModuleMap.mNSString *const JCFirstLevelMapKey = @"JC_firstLevel";NSString *const JCSecondLevelMapKey = @"JC_secondLevel";NSString *const JCThirdLevelMapKey = @"JC_thirdLevel";NSString *const JCContentDetailMapKey = @"JC_contentDetail";@implementation JCTestModuleMap- (NSString *)mapKeyPrefix
{    return @"JC";
}

- (NSDictionary<NSString *,Class> *)classesForMapKeys
{    return @{JCFirstLevelMapKey: NSClassFromString(@"JCFirstLevelViewController"),
             JCSecondLevelMapKey: NSClassFromString(@"JCSecondLevelViewController"),
             JCThirdLevelMapKey: NSClassFromString(@"JCThirdLevelViewController"),
             JCContentDetailMapKey: NSClassFromString(@"JCContentDetailViewController"),
             };
}@end
  • NSURL/mapKey映射原理


    NSURL:mapKey映射原理.jpg

总结:openURL: 通过NSURL及子类化JCModuleMap中实现的mapKeyPrefix拼接对应的mapKey,然后和 openWithMapKey: 一样,都是通过classesForMapKeys方法,获取class-mapKey的映射关系,进而跳转到module中对应class的视图控制器页面。

参数传递

  • 申明接口协议,以属性的方式传递:

@protocol JC_contentDetail <NSObject>@property (nonatomic, strong) NSString *currentIndex;@property (nonatomic, strong) NSString *testId;@property (nonatomic, strong) NSArray *testArray;@end@interface JCContentDetailViewController : UIViewController<JC_contentDetail>@end
  • 通过属性名-属性值生成字典传参:

+ (void)openContentDetailViewControllerWithCurrentIndex:(NSString *)currentIndex testId:(NSString *)testId testArray:(NSArray *)testArray
{    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:3];    if ([currentIndex isKindOfClass:[NSString class]]) {
        params[@"currentIndex"] = currentIndex;
    }    if ([testId isKindOfClass:[NSString class]]) {
        params[@"testId"] = testId;
    }    if ([testArray isKindOfClass:[NSArray class]]) {
        params[@"testArray"] = testArray;
    }
    [[JCNavigator sharedNavigator] openWithMapKey:JCContentDetailMapKey propertiesBlock:^NSDictionary *{        return params;
    } presented:YES animated:YES];
}

页面展示效果设置

  • openURL: 时,通过JCModuleMap子类化实现的方法设置:

// 是否模态弹出(默认NO)- (BOOL)presentedForClass:(Class)viewControllerClass;// 是否有动画(默认YES)- (BOOL)animatedForClass:(Class)viewControllerClass;
  • openWithMapKey:方法调用时直接设置:

// 是否模态及动画- (void)openWithMapKey:(NSString *)mapKey propertiesBlock:(JCNavigatorPropertiesBlock)block presented:(BOOL)presented animated:(BOOL)animated;

Modules之间的解耦是怎么实现的

好了,在实现JCModuleMap子类化及相关跳转配置之后,现在最关键的操作来了,如何为各个modules之间提供通信及调用的接口,才能最大限度的解决解耦的问题?废话不多说,先来看看这段代码:

//  JCNavigator+JCTestModuleInterface.h@interface JCNavigator (JCTestModuleInterface)+ (void)openFirstLevelViewController;

+ (void)openSecondLevelViewController;@end//  JCNavigator+JCTestModuleInterface.m@implementation JCNavigator (JCTestModuleInterface)+ (void)load
{
    [[JCNavigator sharedNavigator] addModuleMap:[JCTestModuleMap new]];
}

+ (void)openFirstLevelViewController
{
    [[JCNavigator sharedNavigator] openWithMapKey:JCFirstLevelMapKey];
}

+ (void)openSecondLevelViewController
{
    [[JCNavigator sharedNavigator] openWithMapKey:JCSecondLevelMapKey];
}@end
  • 从代码可以看出:
    1)每个module声明并实现对应的JCNavigator类别;
    2)在JCNavigator类别中实现load类方法,添加对应子类化JCModuleMap对象;
    3)JCNavigator类别头文件中提供统一的对外接口,实现文件中封装内部调用细节,从而解决modules之间的耦合问题。

  • 下图概述了实现过程:


    Modules接口服务.jpg

总结

对于组件化的介绍,到这里接近尾声,整个解决方案有优点也有缺点,需要开发者各自权衡:

  • 优点
    1)支持Apps之间跳转;
    2)支持Modules之间跳转及通信(参数传递);
    3)所有跳转只基于JCNavigator中间层框架;
    4)可实现Modules之间解耦、互不依赖;
    5)无需额外处理Modules页面间的层级关系。

  • 缺点
    1)需要子类化JCModuleMap,并将实例化对象添加到JCNavigator,增加了内存消耗;
    2)如果viewController类名或传递的参数发生改变,Xcode不会报错也没有警告,需及时维护子类化JCModuleMap的实现,并更新JCNavigator类别中的调用代码。

关于框架的更多实现细节,请关注github开源代码JCNavigator



作者:joych
链接:https://www.jianshu.com/p/5dfc154eac2e

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消