從0到1實(shí)現(xiàn)一個模塊間通信的服務(wù)組件

image
image

寫在前面

一名一線開發(fā)對于App架構(gòu)和組件化的思考 文章中安拟,我們主要站在了軟件工程的角度上,分析了做App架構(gòu)和組件化時該如何下手凳厢,其中也介紹了路由和服務(wù)模塊在組件化中扮演的重要角色。本文料身,我們將進(jìn)行實(shí)操,一步步實(shí)現(xiàn)一個模塊間通信的服務(wù)組件衩茸。

這里剖出一個微服務(wù)的概念芹血,在Java Spring框架中,微服務(wù)是個很火的東西楞慈。鑒于筆者對于Java一概不知幔烛,所以僅僅站在作為一個App開發(fā)的角度去認(rèn)知它。微服務(wù)確切的說是某個功能模塊的子集囊蓝,它把單體架構(gòu)中的某些功能拆離出來饿悬,然后開啟獨(dú)立進(jìn)程來給其他模塊提供服務(wù),通信方式一般是標(biāo)準(zhǔn)REST API來進(jìn)行聚霜。這樣的做的話有幾個好處狡恬。

**1.獨(dú)立進(jìn)程,獨(dú)立部署蝎宇。不會因?yàn)閱误w架構(gòu)機(jī)器掛掉后弟劲,導(dǎo)致所有服務(wù)不可用。 **

**2.避免項(xiàng)目過度臃腫姥芥。 **

3.擴(kuò)展性強(qiáng)兔乞,可以多個微服務(wù)組成集群。

對于Java Spring框架撇眯,這里就不做過多贅述了报嵌。推薦一個比較形象的描述微服務(wù)的漫畫,感興趣的可以看一下熊榛,這樣可以對整個系統(tǒng)上下游架構(gòu)會有更深的理解。

漫畫說:什么是微服務(wù)腕巡?

服務(wù)組件在App里應(yīng)用場景

舉個栗子玄坦。

還是拿登錄模塊舉例子。绘沉。煎楣。

image

在之前的分享中我們知道,登錄模塊一般位于App分層架構(gòu)中的通用模塊層车伞。假如說A模塊要調(diào)用登錄模塊中的獲取登錄態(tài)的方法择懂,在沒有服務(wù)組件的情況下,我們一般會直接把登錄整個模塊import進(jìn)來另玖,這樣做難免有點(diǎn)小尷尬(僅僅是獲取個登錄態(tài)困曙,我就要把整個登錄模塊import進(jìn)來表伦,這樣就耦合在一起)。

再打個很形象的比喻慷丽。蹦哼。。

雖說結(jié)婚不是兩個人的事情要糊,而是兩個家庭的事情纲熏,但是結(jié)婚后你老丈人和丈母娘一起打包過來跟你過了,你是什么感受锄俄?那肯定是臉上笑嘻嘻局劲,心里mmp啊。我是要跟你女兒過日子的啊奶赠,咋都打包給我了鱼填??车柠?剔氏。

image

所以通過以上的生動的示例,我們總結(jié)出了服務(wù)組件在App里的應(yīng)用場景竹祷。

  • 模塊間更小粒度組件間的通信場景谈跛。

  • 開放一個模塊中某些特定功能API場景,使模塊中的子組件“微服務(wù)化”塑陵。

  • 組件化之間進(jìn)行解耦的應(yīng)用場景感憾。

從0到1編寫一個服務(wù)組件

方案一:通知中心(NSNotificationCenter)

Excuse me?通知不是單向數(shù)據(jù)傳輸么,A給B發(fā)通知令花,B收到通知后處理阻桅,貌似不符合我們這種有返回值的需求啊兼都?

在OC中有個神奇的東西那就是Block嫂沉,說白了是匿名函數(shù),那我們直接把函數(shù)指針傳輸過去不就可以了嘛扮碧?而且我們知道在OC中Block本質(zhì)上是一個對象趟章,恰好發(fā)送通知可以攜帶一個對象,豈不美哉慎王。

  • (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;

說寫咱就寫蚓土!Perfect!你為何如此優(yōu)秀@涤佟J衿帷!

登錄模塊:

/*登錄模塊注冊通知*/[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getCookie) name:@"getCookie" object:nil];  - (void)getCookie:(NSNotification *)noti {    void(^callBack)(NSString *) = noti.object;    /*獲取cookie邏輯*/    ---this is a long story---    /*獲取完畢之后咱旱,調(diào)用block*/    if (callBack) {        callBack(@"cookie");    }}

調(diào)用模塊:

/*創(chuàng)建一個Block*/void(^callBack)(NSString *) = ^(NSString *cookie) {     NSLog(@"cookie->%@",cookie); };/*調(diào)用方通過發(fā)送通知*/[[NSNotificationCenter defaultCenter] postNotificationName:@"getCookie" object:callBack];

Command+R确丢,完美绷耍!滿足需求,我們成功地在模塊中獲取到登錄模塊中的登錄態(tài)蠕嫁。

這時候我們停下來仔細(xì)想一下通知中心的方案锨天,假如說登錄模塊除了提供獲取登錄態(tài)的服務(wù),可能還有獲取用戶信息服務(wù)等等剃毒。如果服務(wù)越來越多病袄,注冊通知就會分散在不同的文件中、不同的代碼邏輯中赘阀,服務(wù)太分散難以維護(hù)R娌!基公!

我們總結(jié)了一下幅慌,很容易發(fā)現(xiàn)通知的方案所存在的問題。

  • 注冊通知太分散轰豆,難以維護(hù)胰伍。

  • 沒有統(tǒng)一的地方來維護(hù)通知名稱,調(diào)用方需要預(yù)先知道通知名才能調(diào)用該服務(wù)酸休。

  • 傳參數(shù)不太方便骂租,雖然系統(tǒng)發(fā)送通知函數(shù)提供了一個object,但在復(fù)雜業(yè)務(wù)中遠(yuǎn)遠(yuǎn)不夠斑司。

  • 通知中心存在一定的問題渗饮,比如說不支持異步通知(在A線程注冊通知,B線程發(fā)送通知宿刮,接收到通知后回到A線程進(jìn)行處理)互站。

關(guān)于通知中心的弊端,這里也不做贅述僵缺,推薦一個自己之前寫的一個通知中心解決方案胡桃,目前還不太完善。其使用姿勢相當(dāng)優(yōu)雅磕潮,而且實(shí)現(xiàn)了異步通知标捺,感興趣的筒子們可以了解一下。

SmartBlock(一個用Block實(shí)現(xiàn)的通知替代方案揉抵,并且已實(shí)現(xiàn)在不同線程進(jìn)行發(fā)送消息和執(zhí)行Block,支持多參數(shù)傳送嗤疯,解決回調(diào)地獄問題冤今,適用于組件化數(shù)據(jù)傳輸?shù)取#?/a>

方案二:反射機(jī)制(NSClassFromString)

名詞解釋:Java反射說的是在運(yùn)行狀態(tài)中茂缚,對于任何一個類戏罢,我們都能夠知道這個類有哪些方法和屬性屋谭。對于任何一個對象,我們都能夠?qū)λ姆椒ê蛯傩赃M(jìn)行調(diào)用龟糕。我們把這種動態(tài)獲取對象信息和調(diào)用對象方法的功能稱之為反射機(jī)制桐磁。以上內(nèi)容來自于網(wǎng)絡(luò)。

在OC中讲岁,runtime也提供了類似的機(jī)制我擂,我們可以通過runtime提供的函數(shù),在運(yùn)行時動態(tài)地獲取到某個類缓艳、方法校摩、屬性等。

NSClassFromString(<#NSString * _Nonnull aClassName#>)
NSSelectorFromString(<#NSString * _Nonnull aSelectorName#>)

既然方案一注冊通知太分散阶淘,那我們可不可以對于每個服務(wù)創(chuàng)建一個類衙吩,然后暴露方法,通過runtime反射機(jī)制去調(diào)用溪窒?

  • 第一步:針對獲取登錄態(tài)的服務(wù)單獨(dú)創(chuàng)建類文件坤塞。

  • 第二步:在類文件中開放一個方法供調(diào)用方調(diào)用。

  • 第三步:調(diào)用方通過NSClassFromString獲取到登錄態(tài)的Class澈蚌。

  • 第四步:調(diào)用方通過NSSelectorFromString獲取到登錄態(tài)提供的selector摹芙。

  • 第五步:調(diào)用該方法- (id)performSelector:(SEL)aSelector withObject:(id)object;完成該服務(wù)的調(diào)用。

登錄模塊:

/*我們在登錄模塊創(chuàng)建一個GetLoginCookie類*//*.h和.m如下*/
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface GetLoginCookie : NSObject- (id)getLoginCookieWithObjc:(id)obj;@endNS_ASSUME_NONNULL_END

調(diào)用模塊:

/*在模塊中獲取到GetLoginCookie的Class*/Class cookieCls = NSClassFromString(@"GetLoginCookie");/*通過Class惜浅,生成一個GetLoginCookie實(shí)例*/id cookieInstance = [[cookieCls alloc]init];/*通過方法名生成一個SEL*/SEL selector = NSSelectorFromString(@"getLoginCookieWithObjc:");/*調(diào)用performSelector并獲取返回值*/NSString *cookie = [cookieInstance performSelector:selector withObject:@"it's me!"];NSLog(@"cookie->%@",cookie);

Command + R瘫辩,完美運(yùn)行,我們也得到了我們想要的結(jié)果坛悉。

對比方案一和方案二伐厌,方案二的確解決了服務(wù)分散不好管理的問題,但是依然存在幾個問題裸影。

  • 依然沒有一個配置的地方讓調(diào)用者一下就能看到類名或者sel名挣轨,方便進(jìn)行調(diào)用。

  • 還有個問題轩猩,我們很容易發(fā)現(xiàn)這兩個方案都是“去中心化的”卷扮。也就是說,消息的發(fā)送和消息的接收處理都是直接點(diǎn)對點(diǎn)的均践。去中心化帶來了很多問題晤锹,如果登錄態(tài)的服務(wù)出現(xiàn)問題,而我們又沒有一個統(tǒng)一收口的地方統(tǒng)一處理彤委,不可控鞭铆。

這就好比區(qū)塊鏈技術(shù)去中心化雖然帶來了很多技術(shù)變革,但同樣也帶來了一些隱患焦影。如果沒有上面的的監(jiān)管车遂,那很多black money可以通過區(qū)塊鏈?zhǔn)侄蜗吹絿夥舛稀O胂牒芏嘭澒倌弥覀冃列量嗫嗬U納的稅,把貪來的錢都洗到了國外舶担,然后老婆孩子在國外逍遙自在坡疼,自己在國內(nèi)做luo官。而我們依然活在水深火熱之中衣陶,百姓民不聊生柄瑰,苦不堪言,我們內(nèi)心該是何等氣氛W娲辍S狻!拯欧。

扯多了详囤,我們回到正題。

方案三:引入中間件(IQService)

通過對比前兩個方案镐作,我們大概對于服務(wù)組件應(yīng)該滿足哪些要素有了更加清晰的認(rèn)識藏姐。

  • 服務(wù)組件要易于管理,統(tǒng)一分布在模塊中的某個地方该贾。

  • 服務(wù)組件最好通過配置文件去管理羔杨,方便業(yè)務(wù)方查閱調(diào)用等。

  • 服務(wù)組件去Model化杨蛋,徹底解除兜材、還有支持同步異步調(diào)用等。

  • 服務(wù)組件最好用中間件方式逞力,有統(tǒng)一收口的地方曙寡,發(fā)生問題可控。

  • 服務(wù)組件最好支持靜態(tài)注冊寇荧、動態(tài)注冊等举庶,擴(kuò)展性高。

我們來簡單畫一下揩抡,服務(wù)組件架構(gòu)圖户侥。

image
  • 首先為了解決服務(wù)易于管理問題,我們這里使用plist來維護(hù)業(yè)務(wù)服務(wù)列表和具體服務(wù)名與服務(wù)的對應(yīng)關(guān)系峦嗤。
image

如圖所示蕊唐,IQService.plist維護(hù)了業(yè)務(wù)list,一般IQService主工程維護(hù)一份即可烁设。

image

LoginModule.plist中維護(hù)了該組件為外部提供的所有服務(wù)列表(服務(wù)名和實(shí)現(xiàn)類的對應(yīng)關(guān)系)

  • 去model化刃泌,我們這里用多參數(shù)來解決(也可以通過NSDictionry解決)。
/** 同步、異步調(diào)用 @param sevice 微服務(wù)名 */+ (void)invokeMicroService:(NSString *)sevice,...;/** 同步調(diào)用 @param service 微服務(wù)名 @return 同步調(diào)用返回值 */+ (id)invokeMicroServiceSync:(NSString *)service,...;

我們再來看下具體的使用姿勢耙替。

登錄模塊:

首先創(chuàng)建LoginModuleCookieService類,并將該類注冊到LoginModule中曹体。
.h聲明#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface LoginModuleCookieService : NSObject- (NSString *)getCookieWithSignature:(NSString *)signature;@endNS_ASSUME_NONNULL_END
.m實(shí)現(xiàn)#import "LoginModuleCookieService.h"@implementation LoginModuleCookieService- (NSString *)getCookieWithSignature:(NSString *)signature {    return [NSString stringWithFormat:@"%@->cookie",signature];}@end

調(diào)用模塊:

同步調(diào)用NSString *cookie = [IQService invokeMicroServiceSync:@"GetCookieSyncService",@"我是同步調(diào)用",nil];NSLog(@"%@",cookie);
異步調(diào)用void (^callBack)(NSString *) = ^(NSString *cookie){        NSLog(@"%@",cookie);    };[IQService invokeMicroService:@"GetCookieAsyncService",@"我是異步調(diào)用",callBack,nil];

分析到現(xiàn)在俗扇,方案三基本能滿足大部分業(yè)務(wù)需求。具體實(shí)現(xiàn)代碼已經(jīng)開源到GitHub -----> IQService箕别,一個iOS端模塊間通信的解決方案铜幽。喜歡的筒子可以來波Star??,也歡迎大家提交PR和ISSUE串稀。

騙大家刷完Star除抛,現(xiàn)在再潑盆冷水。母截。到忽。

我們再仔細(xì)思考一下方案三,貌似有幾個問題依然沒有解決

1.編譯時依然無法進(jìn)行參數(shù)正確性校驗(yàn)清寇,attribute喘漏?宏定義?

2.目前只有靜態(tài)注冊华烟,不支持動態(tài)注冊翩迈。

上面兩個問題,歡迎大家進(jìn)行頭腦風(fēng)暴盔夜。有好的解決方案可以留言分享负饲,也可以提交PRs or Issues。https://github.com/Lobster-King/IQService喂链。

在這里提示一點(diǎn)返十,沒有一個方案是100%OK的,只有適合自己的才是最好的衩藤。

架構(gòu)和組件化系列文章預(yù)告:說說MVVM吧慢,會一步步跟大家一起寫一個輕量的view和viewModel進(jìn)行數(shù)據(jù)綁定的框架。

**文章首發(fā)GitHub **https://github.com/Lobster-King/AppArticles

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赏表,一起剝皮案震驚了整個濱河市检诗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓢剿,老刑警劉巖逢慌,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異间狂,居然都是意外死亡攻泼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忙菠,“玉大人何鸡,你說我怎么就攤上這事∨;叮” “怎么了骡男?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長傍睹。 經(jīng)常有香客問我隔盛,道長,這世上最難降的妖魔是什么拾稳? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任吮炕,我火速辦了婚禮,結(jié)果婚禮上访得,老公的妹妹穿的比我還像新娘龙亲。我一直安慰自己,他們只是感情好震鹉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布俱笛。 她就那樣靜靜地躺著,像睡著了一般传趾。 火紅的嫁衣襯著肌膚如雪迎膜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天浆兰,我揣著相機(jī)與錄音磕仅,去河邊找鬼。 笑死簸呈,一個胖子當(dāng)著我的面吹牛榕订,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蜕便,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼劫恒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了轿腺?” 一聲冷哼從身側(cè)響起两嘴,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎族壳,沒想到半個月后憔辫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仿荆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年贰您,在試婚紗的時候發(fā)現(xiàn)自己被綠了坏平。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡锦亦,死狀恐怖舶替,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情孽亲,我是刑警寧澤坎穿,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站返劲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏栖茉。R本人自食惡果不足惜篮绿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吕漂。 院中可真熱鬧亲配,春花似錦、人聲如沸惶凝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苍鲜。三九已至思灰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間混滔,已是汗流浹背洒疚。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坯屿,地道東北人油湖。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像领跛,于是被迫代替她去往敵國和親乏德。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容