iOS 的组件化开发
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住UIApplicationDelegate
和 UNUserNotificationCenterDelegate
中的方法,使相应的组件中实现了对应方法,在相应时机就会调用组建里的对应方法:
#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
之后在AppDelegate
的load
方法中通过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 的组件化开发相关推荐
- iOS组件化开发实践
目录: 1.组件化需求来源 2.组件化初识 3.组件化必备的工具使用 4.模块拆分 5.组件工程兼容swift环境 6.组件之间的通讯 7.组件化后的资源加载 8.OC工程底层换swift代码 9.总 ...
- 【iOS】利用CocoaPods创建私有库进行组件化开发
之前使用CocoaPods管理过第三方库,前面也有翻译文章介绍过CocoaPods的配置方法,随着项目越来越大,很多公司会使用CocoaPods进行组件化开发,下面利用一个demo介绍一下. ###什 ...
- iOS组件化开发流程
iOS组件化开发流程 前期准备工作 注册仓库账号(gitHub/gitLabel/码云) 注册trunk 创建组件模版 在组件模版里编写自己的代码 修改.specs文件 push到仓库 给组件打一个t ...
- iOS组件化开发从开始到完整总结
一.组件化介绍 需求来源 随着项目规模不断扩大,业务模块增多,开发过程中会有多条产品线(多人或多小组开发不同的功能);如果用传统的开发模式,会导致代码臃肿,编译速度越来越慢,开发效率低下,代码维护成本 ...
- 组件化开发,制作Cocoapods Git库
在项目中,如果项目功能很多而且工程浩大,需要多个技术部门或小组同时进行开发,根据独立功能模块进行分配.多个小组开发模块怎么样快速优雅的进行整合到主项目中呢?这个是时候组件化开发优势就体现出来了,每个模 ...
- 58 同城 iOS 客户端组件化演变历程
导语: 架构的演进是为业务不断发展服务的,架构不能脱离业务,这是最基本的出发点.58 同城 iOS 客户端随着业务量和用户量的持续增长,架构也是不断受到挑战,采用什么样的架构去适应这些变化,对技术人员 ...
- 宜人贷-iOS客户端组件化介绍
文章简介: 本文将从三个方面讲解我们组件化项目.第一部分,我们将介绍组件化的意义和业内组件化的进程:第二方面我们将具体介绍组件化所使用的技术,以及组件化过程中所面对的问题:而第三方面,我们会展示我们组 ...
- 组件化开发之-我们有什么必要使用组件化开发?
原创 2016-05-19 背景介绍: 首先简单说一下为什我会写这篇文章呢? 源于今天讨论,提到这个组件化开发和以前没有多大区别,都需要合作编码,共同开发某些相同模块,本来之前都是按照模块划分来开发的 ...
- NOW直播——Flutter组件化开发方案
作者:腾讯NOW直播 -koudleren(任晓帅) 前言 前面讲了Flutter和Native的混合开发模式,Flutter作为Native工程的一个Module存在,这样可以有效的将Flutter ...
最新文章
- python之XML文件解析
- spring读取配置文件的几种方式
- 【TAMU】最新《时间序列分析》课程笔记
- (转)Ubuntu10.04各文件夹的作用
- linux cat pdf文件怎么打开,linux下cat 命令使用详解:显示文件内容
- [转] 学习,思维三部曲:WHAT、HOW、WHY(通过现象看本质)
- 反编译DLL并修改再生成DLL
- JavaScript深入【表达式和运算符(上集)】你能过我8关js运算符的题目吗?
- 数字图像处理实验5图像复原
- frestadmin多样式响应式后台HTML模板
- finally中关闭资源
- Spark分布式安装
- 乾颐堂现任明教教主(2014年课程)TCPIP协议详解卷一 第十节课笔记
- netperf测试最大连接数
- C语言解决狐狸找兔子的问题(数组)
- 详解Spark Streaming的Graceful Shutdown
- 用winpcap实现局域网DNS欺骗
- 事件clientX、pageX、screenX、offsetX
- 大学可以这样读——我的心路历程和一点思考
- 电脑外置,笔记本电脑怎么外接显卡 笔记本外接显卡方法【详解】
热门文章
- 从Elasticsearch来看分布式系统架构设计,真是666~
- 感受lambda之美,推荐收藏,需要时查阅
- 刚出炉的一套面试题(JAVA岗)
- GNN教程:图注意力网络(GAT)详解!
- 并肩XGBoost、LightGBM,一文理解CatBoost!
- 你离开学只差这个视频:李宏毅机器学习2020版正式开放上线
- 腾讯曝光新型AI攻击手法:“黑”掉神经网络,构造后门,最主流模型均不能幸免...
- 一首凉凉送给这些学校硕博生!本学期不必返校!这些高校已经发布通知了
- 1所更名、3所新设!山西4所高校获批复
- 如何在Github上精准地找到想要的开源项目?