在组件化以前,app都是在一个工程里开发的,开发的人员也是比较少的,业务发展也不是很是快,项目中不引用组件化开发也是合适的。可是当开发人员愈来愈多,代码量也就愈来愈多,业务也就愈来愈复杂,这时候单一的开发模式会显露出一些弊端:swift
为了解决这些问题,因而出现了组件化开发的策略,能带来好处以下:缓存
今天咱们讲解一下组件化开发,也是本身项目中使用到的一种方式Target-Action方式。主要是基于Mediator模式和Target-Action,中间采用了Runtime来完成调用。这套组件化将远程和本地应用调用作了拆分,并且是本地调用是为远程调用提供服务。安全
下面是target-action的工做图:架构
本地组件调用:本地组件A在一处调用[[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{...}]向CTMediator发起了跨组件调用,CTMediator根据发送过来的target和action,而后通过OC的runtime机制转为target实例以及action,最后调用到目标业务提供的逻辑,完成要求.app
远程应用的调取:远程应用是经过openURL的方式,由iOS 系统根据info.plist里的scheme配置用来能够找到响应的URL的应用,应用直接经过AppDelegate接收到URL以后,调用了CTMediator的OpenURL方法将接收到的信息传入进去.固然,CTMediator也能够用CTMediator的openURL:options:方式顺便将option也接收,这取决因而否包含了option数据,传入URL以后,CTMediator进行解析URL,将请求的路由到相对应的target-action中,随后的过程就变成了上面的本地应用调用过程了,最终完成了响应.组件化
对应着以下:测试
组件化调用的时候,应该设计是要对参数作去Model化的。若是组件间调用不对的参数作去Model化的设计,就会致使有问题-业务形式上被组件化了,而其实是没有独立。url
若是模块A和模块B采用了Model化的方案,调用方法时传递的参数就是一个对象,所以使用对象化的参数不管是面向接口,带来的结果就是业务模块形式上被组件化了,可是实质上仍是没有被独立。spa
在跨模块场景中,参数最好仍是使用去Model化的的方式去传递,在苹果开发中,也就是以字典的形式去传递。这样就能够作到只有调用方依赖mediator,而响应方是不须要依赖mediator。设计
在去Model的组件化的方案中,影响效率的总有两个:
其实后面问题,不管是否是具备去Model都有这个问题,可是为何要一块儿说了呢,由于下面提供一种方案来将出现的两个问题一块儿解决。
该方案基因而mediator和target-action模式组件化开发,经过运行时完成调用。mediator维护着若干个category,一个category对应着一个target,而一个target能够包含着多个action。
咱们也能够这样理解:一个业务组件包括了一个category组件,这个category中有个mediator的category,而category中有一个target,这个target对应着此业务组件。target中又有若干个接口方法,用来其余业务来获取该业务组件中的业务。
category自己是一种组合模式,根据不一样的分类提供不一样方法,此时每一个组件都是一个分类。
if (indexPath.row == 0) { UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail]; // 得到view controller以后,在这种场景下,到底push仍是present,实际上是要由使用者决定的,mediator只要给出view controller的实例就行了 [self presentViewController:viewController animated:YES completion:nil]; } if (indexPath.row == 1) { UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail]; [self.navigationController pushViewController:viewController animated:YES]; } if (indexPath.row == 2) { // 这种场景下,很明显是须要被present的,因此没必要返回实例,mediator直接present了 [[CTMediator sharedInstance] CTMediator_presentImage:[UIImage imageNamed:@"image"]]; } if (indexPath.row == 3) { // 这种场景下,参数有问题,所以须要在流程中作好处理 [[CTMediator sharedInstance] CTMediator_presentImage:nil]; } if (indexPath.row == 4) { [[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil confirmAction:^(NSDictionary *info) { // 作你想作的事 NSLog(@"%@", info); }]; }
上面就是我对Mediator下target-action模式的基本内容讲解,下面讲解CTMediator代码的具体实现方式!!!
#pragma mark - public methods + (instancetype)sharedInstance { static CTMediator *mediator; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mediator = [[CTMediator alloc] init]; }); return mediator; }
/* scheme://[target]/[action]?[params] url sample: aaa://targetA/actionB?id=1234&title=title [url query]: id=1234&title=title [url path]: /actionB [url host]: targetA */ - (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion { //url参数的处理 NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; // NSString *urlString = [url query]; for (NSString *param in [urlString componentsSeparatedByString:@"&"]) { NSArray *elts = [param componentsSeparatedByString:@"="]; if([elts count] < 2) continue; [params setObject:[elts lastObject] forKey:[elts firstObject]]; } // 这里这么写主要是出于安全考虑,防止黑客经过远程方式调用本地模块。这里的作法足以应对绝大多数场景,若是要求更加严苛,也能够作更加复杂的安全逻辑。 NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""]; if ([actionName hasPrefix:@"native"]) { return @(NO); } // 这个demo针对URL的路由处理很是简单,就只是取对应的target名字和method名字,但这已经足以应对绝大部份需求。若是须要拓展,能够在这个方法调用以前加入完整的路由逻辑 id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO]; if (completion) { if (result) { completion(@{@"result":result}); } else { completion(nil); } } return result; }
/** * 本地组件调用入口 * * @param targetName 类对象 OC中类对象是要Target_为前缀的 * @param actionName 方法名称 最后实际调用的是以Action_为前缀的 * @param params 参数 * @param shouldCacheTarget 是否缓存拼接后的类对象 * * @return return value JSon格式的字符串 */ - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget { //供swift项目使用 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]; } // 拼装方法字符串 NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName]; // 生成SEL SEL action = NSSelectorFromString(actionString); //先从缓存取,取不到去建立,可是也有可能建立失败的状况(targetName值不正确) 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; } } }
注意: 想要调用此方法,定义的类必需要是Target_为前缀的,而且方法必须是Action为前缀的!!!
另外代码也对无响应的状况分了两种状况:
在实际的开发中,能够给无响应的事件提早作一个固定的target,顶上这种特殊状况.
#pragma mark - private methods - (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams { SEL action = NSSelectorFromString(@"Action_response:"); NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init]; NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; params[@"originParams"] = originParams; params[@"targetString"] = targetString; params[@"selectorString"] = selectorString; [self safePerformAction:action target:target params:params]; }
注意:代码中Target_NoTargetAction用来统一处理无响应的时候给的固定的target,action_response就是用来调用的方法.
#pragma mark - private methods - (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams { SEL action = NSSelectorFromString(@"Action_response:"); NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init]; NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; params[@"originParams"] = originParams; params[@"targetString"] = targetString; params[@"selectorString"] = selectorString; [self safePerformAction:action target:target params:params]; }
上面是CTMediator的具体代码,你们能够多看看关于本地组件调用的实现代码!!! 下面讲解本项目中使用到的具体内容.
从智能引擎搜索结果页-交易商详情页界面如上,跨越了两个模块,项目采用了CTMediator的Target-Action模式.下面按照执行的顺序截图以下:
项目采起的方式是MVP架构模式!!!
通过这层层进入,最终获得了viewController
上面就是整个项目中使用的方式,但愿对你们有所帮助!!!