需求來源
頁面跳轉(zhuǎn),主要是Controller的跳轉(zhuǎn),都是一些小的函數(shù),并且需要?jiǎng)?chuàng)建目標(biāo)controller的對象實(shí)例。希望將跳轉(zhuǎn)邏輯集中在一個(gè)地方處理;并且controller之間能夠解耦,不要相互持有。
服務(wù)化思路, 將服務(wù)端SOA架構(gòu)引入客戶端,對于服務(wù)和微服務(wù),如何調(diào)用?采用PC互聯(lián)網(wǎng)的經(jīng)驗(yàn),URL的方式是最成熟的。
組件化思路,分解日益龐大的APP客戶端,如何調(diào)用組件的功能?函數(shù)、代理、block、KVO、通知...?URL純字符的方式,解耦更徹底一點(diǎn),可能更適合一點(diǎn)
第三方庫: routable-ios
在gitHub中輸入router參數(shù),搜索結(jié)果中star排第一的開源庫1323
功能1:打開ViewController
- 設(shè)置navigationController,這個(gè)只要設(shè)置一次,一個(gè)APP只有一個(gè)navigationController
UINavigationController *nav = [[UINavigationController alloc] initWithNibName:nil bundle:nil];
[Routable sharedRouter].navigationController = nav;
- 用map注冊ViewController;格式(format)是 identifier/:key1/:key2,能夠支持中文。
[[Routable sharedRouter] map:@"用戶頁面/:參數(shù)1/:qq" toController:[UserController class]];
- 使用的地方用open函數(shù)調(diào)用;格式(format)是 identifier/value1/value,能夠支持中文。注意這里沒有:
[[Routable sharedRouter] open:@"用戶頁面/dd/值2"];
- 目標(biāo)ViewController重寫函數(shù)initWithRouterParams(初始化函數(shù))或者allocWithRouterParams(從故事版或者Xib中加載)
@implementation UserController
- (id)initWithRouterParams:(NSDictionary *)params {
if ((self = [self initWithNibName:nil bundle:nil])) {
NSLog(@"%@",params);
}
return self;
}
@end
@implementation UserController
+ (id)allocWithRouterParams:(NSDictionary *)params {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
StoryboardController *instance = [storyboard instantiateViewControllerWithIdentifier:@"UserController"];
NSLog(@"%@",params);
return instance;
}
@end
map時(shí)設(shè)定的key值和open時(shí)設(shè)定的value值都在字典參數(shù)params中了
Printing description of params:
{
qq = "\U503c2";
"\U53c2\U65701" = dd;
}
功能2:調(diào)用函數(shù)
- 注冊map:
[[Routable sharedRouter] map:@"invalidate/:id" toCallback:^(NSDictionary *params) {
[Cache invalidate: [params objectForKey:@"id"]]];
}];
2.調(diào)用open:
[[Routable sharedRouter] open:@"invalidate/5h1b2bs"];
功能3:打開外部鏈接
[[Routable sharedRouter] openExternal:@"http://www.youtube.com/watch?v=oHg5SJYRHA0"];
這部分的源碼:
- (void)openExternal:(NSString *)url {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
}
簡評
能夠router ViewController是特色
不能支持標(biāo)準(zhǔn)的URL格式是不足之處
打開外部鏈接功能,聊勝于無,作用不是很大
Routable, an in-app URL router for iOS and Android
第三方庫:MGJRouter
蘑菇街提供的開源庫,根據(jù)HHRouter改寫的,主要功能是函數(shù)調(diào)用。在gitHub上的star數(shù)達(dá)到了513
- 注冊函數(shù):
/**
* routerParameters 里內(nèi)置的幾個(gè)參數(shù)會用到上面定義的 string
*/
typedef void (^MGJRouterHandler)(NSDictionary *routerParameters);
/**
* 注冊 URLPattern 對應(yīng)的 Handler,在 handler 中可以初始化 VC,然后對 VC 做各種操作
*
* @param URLPattern 帶上 scheme,如 mgj://beauty/:id
* @param handler 該 block 會傳一個(gè)字典,包含了注冊的 URL 中對應(yīng)的變量。
* 假如注冊的 URL 為 mgj://beauty/:id 那么,就會傳一個(gè) @{@"id": 4} 這樣的字典過來
*/
+ (void)registerURLPattern:(NSString *)URLPattern toHandler:(MGJRouterHandler)handler;
- 調(diào)用函數(shù):
/**
* 打開此 URL,帶上附加信息,同時(shí)當(dāng)操作完成時(shí),執(zhí)行額外的代碼
*
* @param URL 帶 Scheme 的 URL,如 mgj://beauty/4
* @param parameters 附加參數(shù)
* @param completion URL 處理完成后的 callback,完成的判定跟具體的業(yè)務(wù)相關(guān)
*/
+ (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion;
withUserInfo其實(shí)是傳一個(gè)字典過去,會添加到注冊函數(shù)block的routerParameters參數(shù)中
completion在注冊函數(shù)的block中執(zhí)行,相當(dāng)于實(shí)現(xiàn)了雙向通信,或者說是雙向調(diào)用。
URL可以滿足scheme://host/path?key1=value1&key2=value2的標(biāo)準(zhǔn)形式
path中如果加:,可以傳可變參數(shù),會出現(xiàn)在注冊函數(shù)block的routerParameters參數(shù)中
沒有專門針對ViewController的處理
open函數(shù)的三個(gè)參數(shù)URL,userInfo,completion都會以固定的key值傳到注冊函數(shù)block的routerParameters參數(shù)中
第三方庫:CTMediator
據(jù)說是當(dāng)時(shí)跟蘑菇街Router一樣火的一個(gè)開源庫,gitHub上的star達(dá)到了595
基本思想是將scheme://host/path?key1=value1&key2=value2的標(biāo)準(zhǔn)格式對應(yīng)為native的Module,Class,SEL,parameters
scheme只是解析出來進(jìn)行模塊調(diào)用,用簡單的字符串判斷,是本模塊的就往下執(zhí)行,非本模塊的就直接返回,啥也不干
通過系統(tǒng)API
- (id)performSelector:(SEL)aSelector withObject:(id)object;進(jìn)行方法調(diào)用。調(diào)用結(jié)果通過一個(gè)block回傳,參數(shù)是一個(gè)字典。類名通過API
Class _Nullable NSClassFromString(NSString *aClassName)獲得方法名通過API
SEL NSSelectorFromString(NSString *aSelectorName)得到分遠(yuǎn)程調(diào)用和本地調(diào)用兩種,本地調(diào)用的實(shí)際action以native開頭
遠(yuǎn)程和本地調(diào)用,本質(zhì)上都是用了runtime,是一樣的。提供服務(wù)的類名都以Target_開頭,方法都以action開頭。
不像普通的第三方庫,用Pod中一句話就搞定。按照作者的意思,在真實(shí)的場景中,這里應(yīng)該是三個(gè)不同repo,包括中間件、接口、實(shí)現(xiàn)三部分。在實(shí)際使用中,應(yīng)該用最原始的源文件手動(dòng)導(dǎo)入的方式。
Category目錄在實(shí)際工程中是單獨(dú)的一個(gè)repo,調(diào)用者通過依賴category這個(gè)repo來完成功能調(diào)度。一般來說是每一個(gè)業(yè)務(wù)對應(yīng)一個(gè)category的repo。因此調(diào)用者需要調(diào)度哪個(gè)業(yè)務(wù),就依賴哪個(gè)業(yè)務(wù)的category。category這個(gè)repo由對應(yīng)提供服務(wù)的業(yè)務(wù)來維護(hù)。
CTMediator目錄在實(shí)際工程中也是一個(gè)單獨(dú)的repo,僅用于存放中間件。被每一個(gè)業(yè)務(wù)線各自維護(hù)的category repo所依賴。
DemoModule目錄是實(shí)際提供服務(wù)的業(yè)務(wù),這個(gè)在實(shí)際工程中也是一個(gè)單獨(dú)的repo。這個(gè)repo不被任何人所依賴,這個(gè)repo通過target-action來提供被調(diào)度的功能,然后由category repo通過runtime調(diào)度。
如何選擇?
routable-ios應(yīng)該是最先出來的,其本意應(yīng)該是將分散在APP各個(gè)地方ViewController的跳轉(zhuǎn)集中起來,面向切片編程的思想。這方面是獨(dú)特的。block調(diào)用的功能應(yīng)該是后面順應(yīng)潮流而添加的,其處理方式也很簡單粗暴,就是在其option參數(shù)中增加一個(gè)callback字段,如果有,就直接執(zhí)行返回了?;舅悸肥莄allback或者ViewController,否則就是異常出錯(cuò)了。
HHRouter應(yīng)該是參考routable-ios的實(shí)現(xiàn)原理,對于ViewController部分的功能進(jìn)行了弱化,僅僅是取得這個(gè)對象就好了,沒有跳轉(zhuǎn)的具體實(shí)現(xiàn)。對于block部分的功能進(jìn)行了增強(qiáng),看起來更像標(biāo)準(zhǔn)的URL
MGJRouter是在HHRouter的基礎(chǔ)上進(jìn)一步發(fā)展。完全去除了已經(jīng)淪為雞肋的ViewController部分,繼續(xù)增強(qiáng)block部分。以URL統(tǒng)一處理方法調(diào)用,不論是遠(yuǎn)程的還是本地的調(diào)用。先注冊,再使用。
CTMediator是完全不同的思路。拋棄了URL統(tǒng)一管理的思想,用runtime代替注冊,減少了一半的維護(hù)工作。不過對于類名和方法名的命名上面要注意一下。至于URL,也是做了一層封裝,解析之后,仍然是統(tǒng)一為本地的runtime調(diào)用。iOS應(yīng)用架構(gòu)談 組件化方案是作者推薦的文章,寫得比較清楚。
Object-C選擇CTMediator方案,管理集中的注冊表是一件比較麻煩的事,用Runtime來自動(dòng)完成,省心省力
模塊內(nèi)部還是直接使用方法調(diào)用的形式,簡單直接。
ViewController可以做成router的形式。難復(fù)用,并且還經(jīng)常發(fā)生變化
跟IOS8之后的動(dòng)態(tài)framework結(jié)合起來,中間件可以作為一個(gè)獨(dú)立的基礎(chǔ)模塊,其他的模塊對外接口都做成這種router形式,具體的實(shí)現(xiàn)注意一下類和方法的命名。
動(dòng)態(tài)頁面,由后臺返回需要跳轉(zhuǎn)的目標(biāo)頁面,采用URL的模式包裝
如果考慮用framework進(jìn)行隔離,歸集調(diào)用列表并不是強(qiáng)需求,所以CTMediator的Category是不需要的。這部分主要是target和action名字的hardcode,在framework對外的h文件中進(jìn)行說明就可以了。
Swift選擇蘑菇街方案,protocol是核心,遠(yuǎn)程調(diào)用增加URL解析的過程,runtime不適合在swift中使用
protocol分為協(xié)議本身,協(xié)議使用者,協(xié)議實(shí)現(xiàn)者三部分
考慮與framework結(jié)合,主程序可以調(diào)用framework,但是framework不能調(diào)用主程序,framework之間可以相互調(diào)用。這個(gè)特性需要考慮進(jìn)去。
使用者需要知道協(xié)議內(nèi)容,實(shí)現(xiàn)者也需要知道協(xié)議內(nèi)容,實(shí)現(xiàn)者和使用者可能出現(xiàn)在主程序,也可能出現(xiàn)在framework中。從這個(gè)角度考慮,協(xié)議本身應(yīng)該放在一個(gè)獨(dú)立的framework中,使用者和實(shí)現(xiàn)者只要import一下這個(gè)framework就可以了。
每個(gè)協(xié)議都通過protocol的擴(kuò)展提供默認(rèn)實(shí)現(xiàn)。就算沒有實(shí)現(xiàn)者,協(xié)議也能夠跑起來。這部分內(nèi)容和協(xié)議都放在一個(gè)模塊中。這就是Object-C中“not found”部分。這是swift的語言福利,要好好使用。
協(xié)議用得最多的地方是代理模式,這是一對一的關(guān)系。所以像蘑菇街方案那樣提供一個(gè)使用者和實(shí)現(xiàn)者的配對管理模塊(Module Manager)是十分必要的。這個(gè)應(yīng)該單獨(dú)做一個(gè)framework,作用就像MGJRouter一樣了。使用者和實(shí)現(xiàn)者通過這個(gè)模塊互相知道對方,應(yīng)該是這個(gè)模塊的基本功能。
對于遠(yuǎn)程調(diào)用,URL按照framework or module://class or struct/fucntion?[key : value]這種模式編碼是比較好的做法,類型全部是String。對于提供遠(yuǎn)程調(diào)用的framework,多一步解析URL的步驟,之后,都轉(zhuǎn)入以Protocol為核心的本地調(diào)用模式。至于解析URL這部分功能,可以作為String的擴(kuò)展工具,也可以放在一個(gè)單獨(dú)的framework中作為基礎(chǔ)功能被調(diào)用。
至于安全性,對于scheme部分作為字符串的匹配是應(yīng)該做一下的。再進(jìn)一步,可以對參數(shù)[key:value]部分做下加密,畢竟這部分是放在URL的query參數(shù)部分的。再進(jìn)一步,可以把scheme://后面部分都做一下加解密。
** 如果考慮兼容性,MGJRouter是最有優(yōu)勢的,自由度最大。**
routable-ios要求“page/:id”的格式。
現(xiàn)在項(xiàng)目中URL的格式是"scheme://host?[parameters]",語言是Object-C,為了考慮兼容性,改動(dòng)最小,決定采用MGJRouter并且只用于需要后臺動(dòng)態(tài)配置的頁面。
本地模塊的組件化還沒有開始做,目前還是采用類方法+單利進(jìn)行接口封裝的形式,先把復(fù)用模塊先分出來再說。目前產(chǎn)品還需要支持iOS7,所以framework的應(yīng)用也要延后。