前言
我当时最早是看的是蘑菇街是开源的通过URL来做组件化的。 MGJRouter学习它里面的思路 所有的模块都应该暴露一个文件publicHeader的pch,供其它业务组件来调用的。如果主页需要调用一个URL就通过MGJRouter去publicHeader中找。
所有组件化通信的原理都是暴露服务
- 通过URL形式 :暴露一个Header(暴露当前的组件具有的服务)
- CTMediator: 通过暴露Target(文件),其实Category就是我们需要暴露出来的服务,这样方便调用。
- BeeHive是通过runtime和Mach-O() 来注入的,也是通过暴露服务,先把服务加载到数组或者内存中然后供其它模块调用。
2、MGJRouter
2.1 注册URL
解析URL 通过字典的形式存放Handler Block 存放到全局的数组中 根据当前的规则去遍历数组找到当前的Block
3、CTMediator
暴露Target(文件),通过Category暴露服务,这样方便调用。 CTMediator里面就是核心调用逻辑,最终都是通过CTMediator这个类去调用
// 本地组件调用入口
//targetName -- 类名
//action -- SEL
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
通过NSInvocation
来调用方法,这样的好处就是不需要知道当前具体class
也不需要知道当前的方法编号
,可以通过当前targetName
和actionName
字符串来获得class
和 SEL
(方法编号), 来设置NSInvocation
的参数调用invoke来执行方法。
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
// generate target
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
NSObject *target = self.cachedTarget[targetClassString];
if (target == nil) {
//获得class
Class targetClass = NSClassFromString(targetClassString);
//
target = [[targetClass alloc] init];
}
// generate action
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
if (shouldCacheTarget) {
self.cachedTarget[targetClassString] = target;
}
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
[self.cachedTarget removeObjectForKey:targetClassString];
return nil;
}
}
}
通过NSInvocation执行对应的方法
//通过NSInvocation最终调用方法
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
if(methodSig == nil) {
return nil;
}
const char* retType = [methodSig methodReturnType];
if (strcmp(retType, @encode(void)) == 0) {
//NSInvocation 进行方法调用
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
//对应的方法就执行了
[invocation invoke];
return nil;
}
if (strcmp(retType, @encode(NSInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(BOOL)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
BOOL result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(CGFloat)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
CGFloat result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(NSUInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSUInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}
使用CTMediator
- 1、每一个模块需要一个对应的
Target
文件,来暴露当前模块的服务。 - 2、需要维护一个
CTMediator
分类的Pod库,来为当前模块提供服务。供其它模块调用。 - 3、其它模块调用只需依赖暴露出来的Pod库就可以了。
CTMediator+CTMediatorModuleAActions
是为了解决硬编码的问题。
调用顺序
CTMediator+CTMediatorModuleAActions
===> CTMediator
===> Target_A
===> DemoModuleADetailViewController
4、BeeHive
-
BHAnnotation
注解的方式调用(通过macho 注入)。 -
BHAppDelegate
重写了AppDelegate逻辑 -
BHConfig
上下文配置的环境 -
BHModuleManager
模块的管理(首页、个人中心等模块) -
BHServiceManager
服务的管理 (每个模块暴露出来的方法) -
BHContext
分发它里面的环境BHContext
里面维护了一个servicesByName
字典以Key-Value
的形式存储着,Protocol
和服务(IMP的具体实现)。 -
代理解耦(接管APP Delegate) 分发给不同的模块。
-
模块间的解耦(解除耦合)
4.1 服务注册
4.1.1 MachO 依赖注入注册服务
把当前的BeehiveServices
作为section
字段 ,BeehiveServices
作为section name
注册到Macho
的__DATA
数据段。BeehiveServices
字段存放着对应HomeProtocol
和 BHViewController
。
__DATA
由不同的section
组成, 不同section
又有不同section name
__attribute GNU编译的语法
//used 告诉编译器需要保留的
//函数:TEXT
//数据:DATA(section)
//Macho 文件格式
//
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";
//注册到数据段(Main 函数执行之前执行了)
//@BeeHiveService(HomeServiceProtocol, BHViewController)
//HomeProtocol
#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";
//__DATA BeehiveServices -- Macho 加载的时候添加进去的
/**
char *kHomeProtocol_service __attribute((used, section("__DATA,"#BeehiveServices" "))) =
{\"""HomeProtocol"""\,\""""BHViewController"""\}
*/
5、 模块划分
模块:(首页模块, 个人中心模块) 服务: (每个模块暴露出来的方法)
- 模块:由不同的组件来构成
- 组件:
LGUtils
、LGNetwork
APP职责的划分(最多三层)
底层
:基础层(网络请求、数据缓存)通用业务层
:拿到数据 ===> 清洗数据 ===> 交付数据, 视频播放(单独的Pod库)具体业务层
:首页模块 关注模块
依赖下沉
- 依赖关系应该单一
- 上层只能依赖下层
- 下层不应该知道上层的业务逻辑,如果有这个功能需要单独抽离出来下沉到下层模块