在組件化以前,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
上面就是整個項目中使用的方式,但願對你們有所幫助!!!