- 在iOS中常见的组件化方案有如下几种:
- URL--Scheme:URL页面路由;
- Target--Action:CTMediator
- Protocol--Class:BeeHive
- 实施组件化最主要的目的是为了实现模块之间的解耦,让各个模块依赖于中介者,通过中介者实现模块之间的通信以及传参;
URL--Scheme路由
- 首先介绍一下iOS中的NSURL的部分属性,代码实现如下:
- (void)viewDidLoad {
[super viewDidLoad];
// self.title = @"First";
self.view.backgroundColor = [UIColor cyanColor];
[self addBtn];
NSString *urlString = @"https://www.baidu.com/link?url=dAATY&eqid=81692b77c2ac&pwd=123456";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(@"scheme = %@",url.scheme);
NSLog(@"host = %@",url.host);
NSLog(@"path = %@",url.path);
NSLog(@"query = %@",url.query);
NSLog(@"absoluteURL = %@",url.absoluteURL);
}
- 控制台输出结果如下:
- URL--Scheme路由的第三方库有:JLRoutes,MGJRouter,下面详细介绍JLRoutes的工作原理;
JLRoutes的源码分析
- JLRoutes使用的步骤主要分为两步:
- 第一步:注册页面路由URL,其中主要包含url与block回调;
- 第二步:触发事件,传入页面路由,跳转到指定页面;
第一步:注册页面路由URL
- 在注册页面路由时,首先调用JLRoutes的
+ (instancetype)routesForScheme:(NSString *)scheme
类方法,创建JLRoutes实例对象,实现如下:
+ (instancetype)routesForScheme:(NSString *)scheme
{
JLRoutes *routesController = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
routeControllersMap = [[NSMutableDictionary alloc] init];
});
NSLog(@"%@",routeControllersMap[scheme]);
if (!routeControllersMap[scheme]) {
routesController = [[self alloc] init];
routesController.scheme = scheme;
routeControllersMap[scheme] = routesController;
}
routesController = routeControllersMap[scheme];
return routesController;
}
- 根据路由URL的scheme创建对应的JLRoutes实例对象;
-
routeControllersMap
是静态全局HashMap对象,内部存储的是shceme--JLRoutes
的键值对,其关系见下图所示:
- 然后JLRoutes调用
addRoute:handler
注册路由
与block回调
,核心实现如下:
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary<NSString *, id> *parameters))handlerBlock
{
NSArray <NSString *> *optionalRoutePatterns = [JLRParsingUtilities expandOptionalRoutePatternsForPattern:routePattern];
JLRRouteDefinition *route = [[JLRRouteDefinition alloc] initWithScheme:self.scheme pattern:routePattern priority:priority handlerBlock:handlerBlock];
if (optionalRoutePatterns.count > 0) {
// there are optional params, parse and add them
for (NSString *pattern in optionalRoutePatterns) {
[self _verboseLog:@"Automatically created optional route: %@", route];
JLRRouteDefinition *optionalRoute = [[JLRRouteDefinition alloc] initWithScheme:self.scheme pattern:pattern priority:priority handlerBlock:handlerBlock];
[self _registerRoute:optionalRoute];
}
return;
}
[self _registerRoute:route];
}
- 重点在于:将
scheme,route路由和block回调
封装成一个JLRRouteDefinition
实例对象,然后将其保存在JLRoutes的属性mutableRoutes
路由数组中,关系图见如下绿色部分:
- 第一步注册页面路由的逻辑就结束了,其本质是将页面路由与回调block封装成一个
JLRRouteDefinition
实例对象,然后保存在JLRoutes的属性数组mutableRoutes
中;
第二步:触发事件,传入页面路由,跳转到指定页面
- 假设现在传入的页面路由为
RouteOne://push/FirstNextViewController?titleText=11111111111&name=中国
,URL的参数属性依次为:sheme为RouteOne
host为push
path为FirstNextViewController
query为titleText=11111111111&name=中国
- 触发事件,传入页面路由,调用
[[JLRoutes routesForScheme:@"RouteOne"]routeURL:url]
方法;- 首先根据
scheme
,去routeControllersMap
中获取JLRoutes
实例对象; - 然后
JLRoutes
会根据入参路由URL,调用routeURL:
方法,去路由数组mutableRoutes
进行匹配,找到目标JLRRouteDefinition
对象,然后执行JLRRouteDefinition
中的block回调,核心代码实现如下:
- 首先根据
- (BOOL)_routeURL:(NSURL *)URL withParameters:(NSDictionary *)parameters executeRouteBlock:(BOOL)executeRouteBlock{
if (!URL) {
return NO;
}
[self _verboseLog:@"Trying to route URL %@", URL];
BOOL didRoute = NO;
JLRRouteRequest *request = [[JLRRouteRequest alloc] initWithURL:URL alwaysTreatsHostAsPathComponent:alwaysTreatsHostAsPathComponent];
for (JLRRouteDefinition *route in [self.mutableRoutes copy]) {
// check each route for a matching response
JLRRouteResponse *response = [route routeResponseForRequest:request decodePlusSymbols:shouldDecodePlusSymbols];
if (!response.isMatch) {
continue;
}
[self _verboseLog:@"Successfully matched %@", route];
if (!executeRouteBlock) {
return YES;
}
NSMutableDictionary *finalParameters = [NSMutableDictionary dictionary];
[finalParameters addEntriesFromDictionary:response.parameters];
[finalParameters addEntriesFromDictionary:parameters];
[self _verboseLog:@"Final parameters are %@", finalParameters];
//回调handler
didRoute = [route callHandlerBlockWithParameters:finalParameters];
//匹配 成功 终止循环
if (didRoute) {
// if it was routed successfully, we're done
break;
}
}
if (!didRoute) {
[self _verboseLog:@"Could not find a matching route"];
}
if (!didRoute && self.shouldFallbackToGlobalRoutes && ![self _isGlobalRoutesController]) {
[self _verboseLog:@"Falling back to global routes..."];
didRoute = [[JLRoutes globalRoutes] _routeURL:URL withParameters:parameters executeRouteBlock:executeRouteBlock];
}
if (!didRoute && executeRouteBlock && self.unmatchedURLHandler) {
[self _verboseLog:@"Falling back to the unmatched URL handler"];
self.unmatchedURLHandler(self, URL, parameters);
}
return didRoute;
}
- 可以看到入参URL路由,会首先被封装成一个
JLRRouteRequest
对象,然后在JLRoutes实例对象的路由集合数组mutableRoutes
中,进行遍历匹配,本质是将JLRRouteDefinition
与JLRRouteRequest
进行匹配,将两者封装成JLRRouteResponse
对象,若匹配成功,就会执行block回调方法
,关系见下图红色部分;
- 实际开发中,注册路由的代码如下所示:
[[JLRoutes routesForScheme:"RouteOne"] addRoute:@"/push/:controller"handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
//根据触发事件 传入的页面路由 创建指定的页面控制器
Class class = NSClassFromString(parameters[@"controller"]);
UIViewController *nextVC = [[class alloc] init];
//将页面路由中的参数 传递给页面控制器,实现了页面传参
[self paramToVc:nextVC param:parameters];
//获取当前导航控制器
UIViewController *currentVc = [self currentViewController];
[currentVc.navigationController pushViewController:nextVC animated:YES];
return YES;
}];
-
[self paramToVc:nextVC param:parameters]
实现了将页面路由中的参数,通过Runtime运行时传递给页面控制器,非常巧妙!!!
//传参数
-(void)paramToVc:(UIViewController *)v param:(NSDictionary<NSString *,NSString *> *)parameters{
//runtime将参数传递至需要跳转的控制器
unsigned int outCount = 0;
objc_property_t * properties = class_copyPropertyList(v.class , &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *key = [NSString stringWithUTF8String:property_getName(property)];
NSString *param = parameters[key];
if (param != nil) {
[v setValue:param forKey:key];
}
}
}
- 获取当前导航控制器的代码如下:
- (UIViewController *)currentViewController{
UIViewController * currVC = nil;
UIViewController * Rootvc = self.window.rootViewController ;
do {
if ([Rootvc isKindOfClass:[UINavigationController class]]) {
UINavigationController * nav = (UINavigationController *)Rootvc;
UIViewController * v = [nav.viewControllers lastObject];
currVC = v;
Rootvc = v.presentedViewController;
continue;
}else if([Rootvc isKindOfClass:[UITabBarController class]]){
UITabBarController * tabVC = (UITabBarController *)Rootvc;
currVC = tabVC;
Rootvc = [tabVC.viewControllers objectAtIndex:tabVC.selectedIndex];
continue;
}
} while (Rootvc!=nil);
return currVC;
}
Target-Action -- CTMediator
- 我们知道模块之间的耦合是因为A模块在跳转B模块时,在A模块中需要实例化B模块,造成AB模块之间的耦合绑定;
- CTMediator的工作原理在于:将B模块的实例化,单独放在一个类ClassA的自定义一个方法MethodA中,然后利用CTMediator的分类,在调用方传入参数params,通过
perform:selector
去调用ClassA中MethodA方法,并将参数params传递过去,原理如下图所示:
- 代码实现如下:A模块 跳转 B模块(GoodsDetailViewController)
- 第一步:CTMediator分类方法,需传入调用方的参数params;
@implementation CTMediator (TAGoodsDetail)
- (UIViewController *)goodsDetailViewControllerWithGoodsId:(NSString *)goodsId goodsName:(NSString *)goodsName{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"goodsId"] = goodsId;
params[@"goodsName"] = goodsName;
//核心 利用消息发送的原理 去调用TAGoodsDetail类的GoodsDetailViewController方法
return [self performTarget:@"TAGoodsDetail" action:@"GoodsDetailViewController" params:params shouldCacheTarget:NO];
}
@end
- 第二步:提供实例化B模块的类(Target_TAGoodsDetail)和方法(Action_GoodsDetailViewController:)
#import "Target_TAGoodsDetail.h"
#import "TAGoodsDetailViewController.h"
@implementation Target_TAGoodsDetail
- (UIViewController *)Action_GoodsDetailViewController:(NSDictionary *)params{
TAGoodsDetailViewController *goodsDetailVC = [[TAGoodsDetailViewController alloc] init];
goodsDetailVC.goodsId = params[@"goodsId"];
goodsDetailVC.goodsName = params[@"goodsName"];
return goodsDetailVC;
}
@end
- 第三步:调用方A模块,调用CTMediator分类中方法,并传入页面参数给B模块;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSDictionary *goodsItem = self.dataSource[indexPath.row];
UIViewController *goodsDetailVC = [[CTMediator sharedInstance] goodsDetailViewControllerWithGoodsId:goodsItem[@"goodsId"] goodsName:goodsItem[@"goodsName"]];
if (goodsDetailVC) {
[self.navigationController pushViewController:goodsDetailVC animated:YES];
}
}
Protocol -- Class -- BeeHive
- 面向协议编程,使用BeeHive地方库的主要步骤分为两步:
- 第一步:注册,包括Module模块注册和Services服务注册;
- 第二步:触发事件,调用指定模块与服务;
Module注册
- Module注册分为以下三种方式:
- 以
BHAnnotation.h
文件中的宏定义#define BeeHiveMod(name)
进行注册; - Load类方法注册;
- 读取本地Plist文件进行注册;
- 以
以BHAnnotation中的宏定义注册Module
- 以阿里巴巴官方提供的Demo进行分析,其中
ShopModule
就是以BHAnnotation中的宏定义进行注册的,即@BeeHiveMod(ShopModule)
,BeeHiveMod宏
定义如下:
#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";
- 内部又使用了
BeeHiveDATA宏
,其定义如下:
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
-
@BeeHiveMod(ShopModule)
最终会被表示成此形式:class BeeHive; char * kShopModule_mod ____attribute((used, section("__DATA,"BeehiveMods" "))) = ""ShopModule""
- 此形式的代码,会在
App编译阶段
将ShopModule这个类名写入到Mach-O文件的 __DATA(数据)段中,并以 BeehiveMods 作为key标识位置
; - 在
BHAnnotation.m
文件中,看到以__attribute__((constructor))
修饰的函数方法initProphet()
,此方法会在App运行时调用(且在main函数之间)
,实现如下:
__attribute__((constructor)) void initProphet() {
_dyld_register_func_for_add_image(dyld_callback);
}
- 内部调用dyld系统函数
_dyld_register_func_for_add_image
,此函数会在dyld加载连接镜像文件时频繁调用,传入的回调函数dyld_callback
,实现如下所示:
- 其中访问Mach-O文件,读取Module数据的是调用
BHReadConfiguration
函数,其具体实现如下:
- 上述的逻辑关系总结如下所示:
- 现在马上进入Module的注册流程,主要涉及的类class为
BHModuleManager
,顾名思义Module的管理者;
Services注册
- Services的注册有以下几种方式:
- 以
BHAnnotation.h
文件中的宏定义#define BeeHiveService(servicename,impl)
进行注册;
- 以
以BHAnnotation中的宏定义注册
- 以阿里巴巴官方提供的Demo进行分析,其中
BHViewController
就是以宏进行注册的,即@BeeHiveService(HomeServiceProtocol,BHViewController)
,其中宏定义如下:
#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";
-
@BeeHiveService(HomeServiceProtocol,BHViewController)
,进行宏替换后的形式为:class BeeHive; char * kHomeServiceProtocol_service __attribute((used, section("__DATA,"BeehiveServices" "))) = "{ \""HomeServiceProtocol"\" : \""BHViewController"\"}"
- 此形式的代码,会在
App编译阶段
将"{ \""HomeServiceProtocol"\" : \""BHViewController"\"}"写入到Mach-O文件的 __DATA(数据)段中,并以 BeehiveServices 作为key标识位置
; - 然后在App运行时阶段,读取Mach-O文件中的Services数据进行注册,逻辑与注册Module的逻辑基本相同,逻辑关系如下所示:
- 接下来进入Services的注册流程,主要涉及的类class为
BHServiceManager
,顾名思义Services的管理者,注册服务的核心函数如下所示:
- 上述的关系如下图所示:
Service服务的调用
- 通过
BHServiceManager
调用函数createService:
传入指定的协议,可获取遵循当前指定协议的服务模块(页面),具体实现如下:
- 目前主要是用的是
Service的注册与调用
;- 在App编译期时,往Mach-O文件的数据段,写入需要注册的Service服务即protocol-class的键值对关系;
- 在App运行时,读取Mach-O文件中写入的Service服务,进行服务注册,全部保存在BHServiceManager的属性字典中;
- 调用服务,通过调用BHServiceManager的createService函数,传入指定协议,获取指定模块页面;
参考文章