閱讀本文大概需要 4.55 分鐘
前言
大家知道 Objective-C 本身是沒有支持注解功能的容客,但有時使用注解將大幅提高效率,同時讓代碼更簡單易懂。特別是今天要介紹的一個關(guān)于“微服務(wù)”注冊的場景棒假。
什么是“微服務(wù)”
微服務(wù)是目前后端提的比較多的一個東西,從廣義上來說就是一個去中心化的開發(fā)模式精盅,通過各個組件的自注冊帽哑,達到服務(wù)分發(fā)的效果。
那么跟客戶端有什么關(guān)系呢叹俏?有一個很具體的例子就是“界面路由”妻枕。這也是近期大家談的比較多的一個事(包括我最近也在做),相信很多同學(xué)并不陌生她肯。具體的做法就是佳头,我們會對各個界面定義一個ID,比如首頁-main晴氨、詳情頁-detail康嘉、關(guān)于頁-about等等,然后通過路由管理器來分發(fā)籽前,從而展示指定的Controller亭珍。
路由方案一
一個比較簡單的做法是建立ID到Controller的映射表,然后根據(jù)Controller類名創(chuàng)建對象枝哄,進行push操作:
NSString *controllerClazz = [routeConfig clazzForID:@"main"];
Class controllerClass = NSClassFromString(controllerClazz);
// check the controller class...
UIViewController *controller = [[controllerClass alloc] init];
// initialize parameters...
[topVC pushViewController:controller animated:YES];
這種做法的好處是足夠簡單肄梨,但是規(guī)則太過于死板,無法根據(jù)業(yè)務(wù)做定制挠锥。
一種改進的方案是做一個分發(fā)器众羡,建立ID到展示界面方法的映射。
路由方案二
@implement RouterDispatcher
- (void)dispatchMain {
// 定制
[topVC pushViewController:mainViewController animated:YES];
}
- (void)dispatchDetail {
// 定制
[topVC pushViewController:detailViewController animated:YES];
}
@end
這個方案增加了一層中轉(zhuǎn)蓖租,方便業(yè)務(wù)的定制粱侣,不過缺點也是明顯的羊壹,大量的代碼都堆砌在了 RouterDispatcher
類里。這意味著每次新增界面或者修改業(yè)務(wù)都需要改動到這個類齐婴,顯然作為一個底層核心庫油猫,我們希望最大限度地剝離業(yè)務(wù)以避免改動與保障穩(wěn)定性。如何去掉這個中心化柠偶,就利用到了我們前文所提的“微服務(wù)”思想情妖。
微服務(wù)路由
作為底層框架,我們不想關(guān)心一個ID具體是如何被路由的诱担,我們只提供這種分發(fā)能力毡证,具體的業(yè)務(wù)通過上層注冊來實現(xiàn)。以下面的代碼為例:
@implement RouterDispatcher
static NSMutableDictionary<NSString */*page*/, NSString */*dispatcherClazz*/> kPages;
+ (void)registerPage:(NSString *)pageID {
if (kPages == nil) {
kPages = [[NSMutableDictionary alloc] init];
}
kPages[page] = NSStringFromClass([self class]);
}
+ (void)dispatchPage:(NSString *)pageID {
NSString *dispatcherClazz = kPages[pageID];
if (dispatcherClazz == nil) {
return;
}
Class dispatcherClass = NSClassFromString(dispatcherClazz);
RouterDispatcher dispatcher = [[dispatcherClass alloc] init];
[dispatcher dispatch];
}
@end
我們只提供了 registerPage
注冊方法以及 dispatchPage
分發(fā)方法该肴。dispatchPage
方法的實現(xiàn)很簡單:根據(jù)已注冊的分發(fā)器做轉(zhuǎn)發(fā)情竹。
那么如何以“微服務(wù)”的形式做注冊呢?
我們注意到了 NSObject
的 load
方法匀哄,這個方法會在類被加載的時候執(zhí)行秦效,顯然用來做服務(wù)注冊是再合適不過了——類被加載了意味著這個類可用,與此同時注冊上服務(wù)意味著服務(wù)也是可用的涎嚼。這也符合“微服務(wù)”啟動自注冊的理念阱州。
終上,要完成對 "main" 的路由法梯,只需要以下 3 個步驟:
- 繼承 RouterDispatcher 實現(xiàn) MainRouterDispatcher
- 使用
load
來注冊 "main" 服務(wù) - 實現(xiàn)
dispatch
完成路由分發(fā)
@implement MainRouterDispatcher
+ (void)load {
[self registerPage:@"main"];
}
- (void)dispatch {
// 業(yè)務(wù)邏輯
[topVC pushViewController:mainViewController animated:YES];
}
@end
好了苔货,現(xiàn)在底層框架基本OK了,但是對于一線開發(fā)來說立哑,重復(fù)的寫 + (void) load
顯然是件很啰嗦的事夜惭,而且看上去不夠醒目,容易被忽略铛绰。那么這個時候就是“注解”一展身手的時候了诈茧。
使用注解
最終的效果是:
@page(@"main")
- (void)dispatch {
// Do stuff...
}
首先,注解的基本格式為 @annotation(attr)
捂掰,官方常見的一些 @
打頭的關(guān)鍵字包括:
- @property
- @synthesize
- @dynamic
- @interface
- @implement
我們可以使用宏來做替換敢会,比如定義了:
#define my_property property
就可以使用 @my_property
來替代 @property
,達到一樣的效果这嚣。
但是這里有一個前提鸥昏,我們選用的 @xx
需要在 @implement
@end
區(qū)間內(nèi)部來使用,比較符合的是 @synthesize
姐帚、@dynamic
吏垮,但缺點是其后面必須帶上一個屬性,比如 @synthesize title;
,如果當(dāng)前類沒有屬性就無法定義惫皱。
這個時候像樊,我們注意到了一個不常用的 @compatibility_alias
。這個注解是用于類名兼容的旅敷,一般開發(fā)不會用到。不過在框架開發(fā)中可能派上用場颤霎。
講到這里媳谁,順便提下我在開發(fā) Pbind 過程中的一個小插曲。當(dāng)時Pbind內(nèi)部實現(xiàn)了一個類 PBRequest 用來統(tǒng)一封裝API請求友酱,突然有一天發(fā)現(xiàn) Apple 的私有庫 ProtocolBuffer.framework 也有這么一個同名的類晴音,控制臺輸出警告:“類名沖突,系統(tǒng)會選擇其中一個而忽略另一個”缔杉。這就尷尬了锤躁,誰知道你哪天選哪個呢?保險起見或详,只能自己換掉系羞,初步的想法是用 _ PBRequest 替換 PBRequest,但是回頭想想這個類是面向開發(fā)者的霸琴,我一大波的 PB 打頭類椒振,突然碰上一個 _ PB 打頭的不是很奇怪么?偶然間發(fā)現(xiàn)了 @compatibility_alias
神器梧乘,兩步即可搞定:
- 修改 PBRequest 為 _PBRequest (.h 跟 .m文件)
- 在修改后的 _PBRequest.h 文件中加上一句:
@compatibility_alias PBRequest _PBRequest;
于是澎迎,其他的 所有 引用到 PBRequest 的地方都不需要改動,甚至接入這個庫的使用者依然可以直接使用 PBRequest选调,因為大家都處于同一個編譯環(huán)境下夹供。
OK,回到我們的話題上來仁堪,我們來使用這個神奇的 @compatibility_alias
完成我們的“注解”:
#define page(_pageID_) \
compatibility_alias _RouterDispatcher RouterDispatcher; \
+ (void)load { \
[self registerPage:_pageID_]; \
}
通過上述定義哮洽,使用 @page(@"main")
時將會被展開:
@compatibility_alias _RouterDispatcher RouterDispatcher;
+ (void)load {
[self registerPage:@"main"];
}
相比我們最初給出的代碼,這里唯一添加的一句“廢話”就是:@compatibility_alias _RouterDispatcher RouterDispatcher;
即允許你在當(dāng)前環(huán)境下使用 _“RouterDispatcher” 類枝笨,顯然你不會用到它袁铐。不過我們也不需要過多地關(guān)注它,這句代碼是在編譯時做的横浑,并不會影響到運行時剔桨。
OK,現(xiàn)在我們可以很方便地使用這個注解在任何地方徙融,完成各個界面的路由洒缀。更重要的是 load
方法是系統(tǒng)加載類時自動觸發(fā)的,這意味著你可以把分發(fā)器實現(xiàn)在各個地方,包括你所實施的組件化的某一個 Library 或者 Framework 里树绩。
總結(jié)
本文介紹了“微服務(wù)”的基本思想萨脑,通過 load
方法實現(xiàn)了 iOS 微服務(wù)組件的自注冊。再結(jié)合宏與 @compatibility_alias
完成了 iOS 的“注解”功能饺饭。達到“注解”實現(xiàn)“微服務(wù)”路由的效果:
@page(@"main")
- (void)dispatch {
// Do stuff...
}
Pbind 是一個支持 LiveLoad 的高度可配置化框架渤早,以上代碼實踐均源自 Pbind 的開發(fā)過程,關(guān)于注解注冊服務(wù)的部分還可以參考 Pbind 源碼中的 PBAction 以及 PBClient 的實現(xiàn)瘫俊。