- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
target-action
这个方案是基于OC的runtime、category特性动态获取模块,例如通过NSClassFromString获取类并创建实例,通过performSelector + NSInvocation动态调用方法
其主要的代表框架是casatwy的 CTMediator。
其实现思路是:
- 利用分类为路由添加新接口,在接口中通过字符串获取对应的类
- 通过runtime创建实例,动态调用实例的方法
//******* 1、分类定义新接口
extension CTMediator{
@objc func A_showHome()->UIViewController?{
let params = [
kCTMediatorParamsKeySwiftTargetModuleName: "CJLBase_Example"
]
if let vc = self.performTarget("A", action: "Extension_HomeViewController", params: params, shouldCacheTarget: false) as? UIViewController{
return vc
}
return nil
}
}
//******* 2、模块提供者提供target-action的调用方式(对外需要加上public关键字)
class Target_A: NSObject {
@objc func Action_Extension_HomeViewController(_ params: [String: Any])->UIViewController{
let home = HomeViewController()
return home
}
}
//******* 3、使用
if let vc = CTMediator.sharedInstance().A_showHome() {
self.navigationController?.pushViewController(vc, animated: true)
}
优点
- 利用 分类 可以明确声明接口,进行编译检查
- 实现方式轻量
缺点
- 需要在mediator 和 target中重新添加每一个接口,模块化时代码较为繁琐
- 在 category 中仍然引入了字符串硬编码,内部使用字典传参,一定程度上也存在和 URL 路由相同的问题
- 无法保证使用的模块一定存在,target在修改后,使用者只能在运行时才能发现错误
源码分析 这里会调用CTMediator分类中的方法。
这里调用performTarget 创建了一个ViewController。
这里根据传进来的参数targetName进行类的生成,然后生成这个类的对象。接着用这个对象根据传进来的actionName和params进行方法的调用,然后返回方法的返回值。
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
if (targetName == nil || actionName == nil) {
return nil;
}
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 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;
}
}
}
这里对返回值进行了判断,返回相对应的返回值。
流程图: