2019独角兽企业重金招聘Python工程师标准>>>

在一个APP开发过程中,如果项目较小且团队人数较少,使用最基本的MVC、MVVM开发就已经足够了,因为维护成本比较低。

但是当一个项目开发团队人数较多时,因为每个人都会负责相应组件的开发,常规开发模式耦合会越来越严重,而且导致大量代码冲突,会使后期维护和升级过程中代码“牵一发而动全身”,额外带来很大的工作量,并且会导致一些潜在的BUG。

在这时,组件化开发就派上很大用场了,所谓的组件化开发,就是把APP根据业务拆分为各独立的组件,各个组件相互写作,组成完整的APP。

一、各组件的引入

关于组件的拆分,就根据具体项目进行拆分,假如APP被拆分了AModule、BModule、CModule,那么,应该如何引入这些组件呢?你可能会想到APP的入口AppDelegate。在平时开发中,AppDelegate中往往初始化了好多组件,比如推送、统计等组件,这样就会导致AppDelegate的臃肿。

所以,我们可以增加一个ModuleManager,专门用来初始化各组件。 首先增加一个 ModuleProtocol

#import <Foundation/Foundation.h>
@import UIKit;
@import UserNotifications;@protocol ModuleProtocol <UIApplicationDelegate, UNUserNotificationCenterDelegate>@end

我们在ModuleManager中hook住UIApplicationDelegateUNUserNotificationCenterDelegate中的方法,使相应的组件中实现了对应方法,在相应时机就会调用组建里的对应方法:

#import "ModuleManager.h"
#import "AppDelegate.h"
#import <objc/runtime.h>#define ALL_MODULE [[ModuleManager sharedInstance] allModules]
#define SWIZZLE_METHOD(m) swizzleMethod(class, @selector(m),@selector(module_##m));@interface ModuleManager ()@property (nonatomic, strong) NSMutableArray<id<ModuleProtocol>> *modules;@end@implementation ModuleManager+ (instancetype)sharedInstance { ...... }- (NSMutableArray<id<ModuleProtocol>> *)modules { ...... }- (void)addModule:(id<ModuleProtocol>) module { ...... }- (void)loadModulesWithPlistFile:(NSString *)plistFile { ...... }- (NSArray<id<ModuleProtocol>> *)allModules { ...... }@end@implementation AppDelegate (Module)+ (void)load
{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Class class = [self class];SWIZZLE_METHOD(application:willFinishLaunchingWithOptions:);SWIZZLE_METHOD(application:didFinishLaunchingWithOptions:);......});
}static inline void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) { ...... }- (BOOL)module_application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{BOOL result = [self module_application:application willFinishLaunchingWithOptions:launchOptions];for (id<ModuleProtocol> module in ALL_MODULE) {if ([module respondsToSelector:_cmd]) {[module application:application willFinishLaunchingWithOptions:launchOptions];}}return result;
}- (BOOL)module_application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{BOOL result = [self module_application:application didFinishLaunchingWithOptions:launchOptions];for (id<ModuleProtocol> module in ALL_MODULE) {if ([module respondsToSelector:_cmd]) {[module application:application didFinishLaunchingWithOptions:launchOptions];}}return result;
}
......@end

ModuleManager.h:

#import <Foundation/Foundation.h>
#import "ModuleProtocol.h"@interface ModuleManager : NSObject+ (instancetype)sharedInstance;- (void)loadModulesWithPlistFile:(NSString *)plistFile;- (NSArray<id<ModuleProtocol>> *)allModules;@end

之后我们通过一个 ModulesRegister.plist文件管理需要引入的组件:

如上图,假如我们要引入AModule、BModule、CModule,那么这三个Module只需要实现协议ModuleProtocol,然后实现AppDelegate中对应的方法,在对应方法中初始化自身即可: AModule.h:

#import <Foundation/Foundation.h>
#import "ModuleProtocol.h"@interface AModule : NSObject<ModuleProtocol>@end

AModule.m:

#import "AModule.h"@implementation AModule- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{//初始化AModulereturn YES;
}@end

之后在AppDelegateload方法中通过ModulesRegister.plist引入各组件即可:

@implementation AppDelegate+ (void)load {//load modulesNSString* plistPath = [[NSBundle mainBundle] pathForResource:@"ModulesRegister" ofType:@"plist"];[[ModuleManager sharedInstance] loadModulesWithPlistFile:plistPath];
}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {......
}@end

这样,各组件的开发者在自己的组件中初始化自己,其他人需要使用时只需要加入ModulesRegister.plist文件中即可。

二、组件间协作

简单来看,假设APP的每个页面就是一个组件,假如我们的APP有AViewController、BViewController、CViewController、DViewController、EViewController,各ViewController必然设置各种相互跳转。那么,我们APP的跳转逻辑可能是下面这个样子:

为了解决这种复杂的耦合关系,我们可以增加一个Router中间层去管理各ViewController之间的跳转关系(也就是实际开发中组件间相互调用的关系)。

所以,根据需要,我开发并开源了一个支持URL Rewrite的iOS路由库— FFRouter,通过FFRouter去管理各ViewController之间的跳转关系:

这样,各ViewController之间的跳转关系就变的清晰了许多。

FFRouter通过提前注册对应的URL,之后就直接通过打开URL去控制各ViewController之间的跳转(或各组件间的调用)。 FFRouter支持组件间传递非常规对象,如UIImage等,并支持获取组件返回值。 基本使用如下:

/**注册 url@param routeURL 要注册的 URL@param handlerBlock URL 被 Route 后的回调*/
+ (void)registerRouteURL:(NSString *)routeURL handler:(FFRouterHandler)handlerBlock;/**注册 URL,通过该方式注册的 URL 被 Route 后可返回一个 Object@param routeURL 要注册的 URL@param handlerBlock URL 被 Route 后的回调,可在回调中返回一个 Object*/
+ (void)registerObjectRouteURL:(NSString *)routeURL handler:(FFObjectRouterHandler)handlerBlock;/**判断 URL 是否可被 Route(是否已经注册)@param URL 要判断的 URL@return 是否可被 Route*/
+ (BOOL)canRouteURL:(NSString *)URL;/**Route 一个 URL@param URL 要 Router 的 URL*/
+ (void)routeURL:(NSString *)URL;/**Route 一个 URL,并带上额外参数@param URL 要 Router 的 URL@param parameters 额外参数*/
+ (void)routeURL:(NSString *)URL withParameters:(NSDictionary<NSString *, id> *)parameters;/**Route 一个 URL,可获得返回的 Object@param URL 要 Router 的 URL@return 返回的 Object*/
+ (id)routeObjectURL:(NSString *)URL;/**Route 一个 URL,并带上额外参数,可获得返回的 Object@param URL 要 Router 的 URL@param parameters 额外参数@return 返回的 Object*/
+ (id)routeObjectURL:(NSString *)URL withParameters:(NSDictionary<NSString *, id> *)parameters;/**Route 一个未注册 URL 时回调@param handler 回调*/
+ (void)routeUnregisterURLHandler:(FFRouterUnregisterURLHandler)handler;/**取消注册某个 URL@param URL 要被取消注册的 URL*/
+ (void)unregisterRouteURL:(NSString *)URL;/**取消注册所有 URL*/
+ (void)unregisterAllRoutes;/**是否显示 Log,用于调试@param enable YES or NO,默认为 NO*/
+ (void)setLogEnabled:(BOOL)enable;

而且参考天猫的方案增加了URL Rewrite功能: 可以使用正则添加一条 Rewrite 规则,例如: 要实现打开 URL:https://www.taobao.com/search/原子弹时,将其拦截,改用本地已注册的 URL:protocol://page/routerDetails?product=原子弹打开。 首先添加一条 Rewrite 规则:

[FFRouterRewrite addRewriteMatchRule:@"(?:https://)?www.taobao.com/search/(.*)" targetRule:@"protocol://page/routerDetails?product=$1"];

之后在打开URL:https://www.taobao.com/search/原子弹时,将会 Rewrite 到URL:protocol://page/routerDetails?product=原子弹

[FFRouter routeURL:@"https://www.taobao.com/search/原子弹"];

可以通过以下方法同时增加多个规则:

+ (void)addRewriteRules:(NSArray<NSDictionary *> *)rules;

其中 rules 格式必须为以下格式:

@[@{@"matchRule":@"YourMatchRule1",@"targetRule":@"YourTargetRule1"},@{@"matchRule":@"YourMatchRule2",@"targetRule":@"YourTargetRule2"},@{@"matchRule":@"YourMatchRule3",@"targetRule":@"YourTargetRule3"},]

Rewrite 规则中的保留字:

  • 通过 $scheme$host$port$path$query$fragment 获取标准 URL 中的相应部分。通过$url获取完整 URL
  • 通过 $1$2$3...获取matchRule的正则中使用圆括号取出的参数
  • $:原变量的值、$$:原变量URL Encode后的值、$#:原变量URL Decode后的值

例如: https://www.taobao.com/search/原子弹对于Rewrite 规则(?:https://)?www.taobao.com/search/(.*)

$1=原子弹
$$1=%e5%8e%9f%e5%ad%90%e5%bc%b9

同样,https://www.taobao.com/search/%e5%8e%9f%e5%ad%90%e5%bc%b9对于Rewrite 规则(?:https://)?www.taobao.com/search/(.*)

$1=%e5%8e%9f%e5%ad%90%e5%bc%b9
$#1=原子弹

考虑到经常用路由配置UIViewController之间的跳转,所以增加了额外的工具FFRouterNavigation来更方便地控制UIViewController之间的跳转。

三、其他组件化方案

目前这种组件化方案参考了蘑菇街、天猫、京东的的实现方案。除这种方案外,Casa(查看文章)之前提出了解耦程度更高的方案,这种方案组件仍然使用中间件通信,但中间件通过 runtime 接口解耦,然后使用 target-action 简化写法,通过 category 分离组件接口代码。 但是,这种方案虽然解耦程度更高,但是也增加了组件化的成本,综合考虑,直接使用中间件通信的方式更好一点。具体哪种方案好,也就仁者见仁、智者见智了~

转载于:https://my.oschina.net/u/2448717/blog/2222619

iOS 的组件化开发相关推荐

  1. iOS组件化开发实践

    目录: 1.组件化需求来源 2.组件化初识 3.组件化必备的工具使用 4.模块拆分 5.组件工程兼容swift环境 6.组件之间的通讯 7.组件化后的资源加载 8.OC工程底层换swift代码 9.总 ...

  2. 【iOS】利用CocoaPods创建私有库进行组件化开发

    之前使用CocoaPods管理过第三方库,前面也有翻译文章介绍过CocoaPods的配置方法,随着项目越来越大,很多公司会使用CocoaPods进行组件化开发,下面利用一个demo介绍一下. ###什 ...

  3. iOS组件化开发流程

    iOS组件化开发流程 前期准备工作 注册仓库账号(gitHub/gitLabel/码云) 注册trunk 创建组件模版 在组件模版里编写自己的代码 修改.specs文件 push到仓库 给组件打一个t ...

  4. iOS组件化开发从开始到完整总结

    一.组件化介绍 需求来源 随着项目规模不断扩大,业务模块增多,开发过程中会有多条产品线(多人或多小组开发不同的功能);如果用传统的开发模式,会导致代码臃肿,编译速度越来越慢,开发效率低下,代码维护成本 ...

  5. 组件化开发,制作Cocoapods Git库

    在项目中,如果项目功能很多而且工程浩大,需要多个技术部门或小组同时进行开发,根据独立功能模块进行分配.多个小组开发模块怎么样快速优雅的进行整合到主项目中呢?这个是时候组件化开发优势就体现出来了,每个模 ...

  6. 58 同城 iOS 客户端组件化演变历程

    导语: 架构的演进是为业务不断发展服务的,架构不能脱离业务,这是最基本的出发点.58 同城 iOS 客户端随着业务量和用户量的持续增长,架构也是不断受到挑战,采用什么样的架构去适应这些变化,对技术人员 ...

  7. 宜人贷-iOS客户端组件化介绍

    文章简介: 本文将从三个方面讲解我们组件化项目.第一部分,我们将介绍组件化的意义和业内组件化的进程:第二方面我们将具体介绍组件化所使用的技术,以及组件化过程中所面对的问题:而第三方面,我们会展示我们组 ...

  8. 组件化开发之-我们有什么必要使用组件化开发?

    原创 2016-05-19 背景介绍: 首先简单说一下为什我会写这篇文章呢? 源于今天讨论,提到这个组件化开发和以前没有多大区别,都需要合作编码,共同开发某些相同模块,本来之前都是按照模块划分来开发的 ...

  9. NOW直播——Flutter组件化开发方案

    作者:腾讯NOW直播 -koudleren(任晓帅) 前言 前面讲了Flutter和Native的混合开发模式,Flutter作为Native工程的一个Module存在,这样可以有效的将Flutter ...

最新文章

  1. python之XML文件解析
  2. spring读取配置文件的几种方式
  3. 【TAMU】最新《时间序列分析》课程笔记
  4. (转)Ubuntu10.04各文件夹的作用
  5. linux cat pdf文件怎么打开,linux下cat 命令使用详解:显示文件内容
  6. [转] 学习,思维三部曲:WHAT、HOW、WHY(通过现象看本质)
  7. 反编译DLL并修改再生成DLL
  8. JavaScript深入【表达式和运算符(上集)】你能过我8关js运算符的题目吗?
  9. 数字图像处理实验5图像复原
  10. frestadmin多样式响应式后台HTML模板
  11. finally中关闭资源
  12. Spark分布式安装
  13. 乾颐堂现任明教教主(2014年课程)TCPIP协议详解卷一 第十节课笔记
  14. netperf测试最大连接数
  15. C语言解决狐狸找兔子的问题(数组)
  16. 详解Spark Streaming的Graceful Shutdown
  17. 用winpcap实现局域网DNS欺骗
  18. 事件clientX、pageX、screenX、offsetX
  19. 大学可以这样读——我的心路历程和一点思考
  20. 电脑外置,笔记本电脑怎么外接显卡 笔记本外接显卡方法【详解】

热门文章

  1. 从Elasticsearch来看分布式系统架构设计,真是666~
  2. 感受lambda之美,推荐收藏,需要时查阅
  3. 刚出炉的一套面试题(JAVA岗)
  4. GNN教程:图注意力网络(GAT)详解!
  5. 并肩XGBoost、LightGBM,一文理解CatBoost!
  6. 你离开学只差这个视频:李宏毅机器学习2020版正式开放上线
  7. 腾讯曝光新型AI攻击手法:“黑”掉神经网络,构造后门,最主流模型均不能幸免...
  8. 一首凉凉送给这些学校硕博生!本学期不必返校!这些高校已经发布通知了
  9. 1所更名、3所新设!山西4所高校获批复
  10. 如何在Github上精准地找到想要的开源项目?