從0到1實現一個模塊間通訊的服務組件

寫在前面

一名一線開發對於App架構和組件化的思考 文章中,咱們主要站在了軟件工程的角度上,分析了作App架構和組件化時該如何下手,其中也介紹了路由和服務模塊在組件化中扮演的重要角色。本文,咱們將進行實操,一步步實現一個模塊間通訊的服務組件。git

這裏剖出一個微服務的概念,在Java Spring框架中,微服務是個很火的東西。鑑於筆者對於Java一律不知,因此僅僅站在做爲一個App開發的角度去認知它。微服務確切的說是某個功能模塊的子集,它把單體架構中的某些功能拆離出來,而後開啓獨立進程來給其餘模塊提供服務,通訊方式通常是標準REST API來進行。這樣的作的話有幾個好處。github

1.獨立進程,獨立部署。不會由於單體架構機器掛掉後,致使全部服務不可用。
2.避免項目過分臃腫。
3.擴展性強,能夠多個微服務組成集羣。bash

對於Java Spring框架,這裏就不作過多贅述了。推薦一個比較形象的描述微服務的漫畫,感興趣的能夠看一下,這樣能夠對整個系統上下游架構會有更深的理解。cookie

漫畫說:什麼是微服務?架構

服務組件在App裏應用場景

舉個栗子🌰。
仍是拿登陸模塊舉例子。。。框架

在以前的分享中咱們知道,登陸模塊通常位於App分層架構中的通用模塊層。假如說A模塊要調用登陸模塊中的獲取登陸態的方法,在沒有服務組件的狀況下,咱們通常會直接把登陸整個模塊import進來,這樣作不免有點小尷尬(僅僅是獲取個登陸態,我就要把整個登陸模塊import進來,這樣就耦合在一塊兒)。異步

再打個很形象的比喻。。。
雖然說結婚不是兩我的的事情,而是兩個家庭的事情,可是結婚後你老丈人和丈母孃一塊兒打包過來跟你過了,你是什麼感覺?那確定是臉上笑嘻嘻,內心mmp啊。我是要跟你女兒過日子的啊,咋都打包給我了???😄。函數

因此經過以上的生動的示例,咱們總結出了服務組件在App裏的應用場景。微服務

  • 模塊間更小粒度組件間的通訊場景。
  • 開放一個模塊中某些特定功能API場景,使模塊中的子組件「微服務化」。
  • 組件化之間進行解耦的應用場景。

從0到1編寫一個服務組件

方案一:通知中心(NSNotificationCenter)組件化

Excuse me?通知不是單向數據傳輸麼,A給B發通知,B收到通知後處理,貌似不符合咱們這種有返回值的需求啊?

在OC中有個神奇的東西那就是Block,說白了是匿名函數,那咱們直接把函數指針傳輸過去不就能夠了嘛?並且咱們知道在OC中Block本質上是一個對象,剛好發送通知能夠攜帶一個對象,豈不美哉。

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

說寫咱就寫!Perfect!你爲什麼如此優秀!!!

登陸模塊:

/*登陸模塊註冊通知*/
[[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---
    /*獲取完畢以後,調用block*/
    if (callBack) {
        callBack(@"cookie");
    }
}
複製代碼

調用模塊:

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

複製代碼

Command+B,完美!知足需求,咱們成功地在模塊中獲取到登陸模塊中的登陸態。

這時候咱們停下來仔細想一下通知中心的方案,假如說登陸模塊除了提供獲取登陸態的服務,可能還有獲取用戶信息服務等等。若是服務愈來愈多,註冊通知就會分散在不一樣的文件中、不一樣的代碼邏輯中,服務太分散難以維護!!!

咱們總結了一下,很容易發現通知的方案所存在的問題。

  • 註冊通知太分散,難以維護。
  • 沒有統一的地方來維護通知名稱,調用方須要預先知道通知名才能調用該服務。
  • 傳參數不太方便,雖然系統發送通知函數提供了一個object,但在複雜業務中遠遠不夠。
  • 通知中心存在必定的問題,好比說不支持異步通知(在A線程註冊通知,B線程發送通知,接收到通知後回到A線程進行處理)。

關於通知中心的弊端,這裏也不作贅述,推薦一個本身以前寫的一個通知中心解決方案,目前還不太完善。其使用姿式至關優雅,並且實現了異步通知,感興趣的筒子們能夠了解一下。

SmartBlock(一個用Block實現的通知替代方案,而且已實如今不一樣線程進行發送消息和執行Block,支持多參數傳送,解決回調地獄問題,適用於組件化數據傳輸等。)

方案二:反射機制(NSClassFromString)

名詞解釋:Java反射說的是在運行狀態中,對於任何一個類,咱們都可以知道這個類有哪些方法和屬性。對於任何一個對象,咱們都可以對它的方法和屬性進行調用。咱們把這種動態獲取對象信息和調用對象方法的功能稱之爲反射機制。以上內容來自於網上。

在OC中,runtime也提供了相似的機制,咱們能夠經過runtime提供的函數,在運行時動態地獲取到某個類、方法、屬性等。

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

既然方案一註冊通知太分散,那咱們可不能夠對於每一個服務建立一個類,而後暴露方法,經過runtime反射機制去調用?

  • 第一步:針對獲取登陸態的服務單首創建類文件。
  • 第二步:在類文件中開放一個方法供調用方調用。
  • 第三步:調用方經過NSClassFromString獲取到登陸態的Class。
  • 第四步:調用方經過NSSelectorFromString獲取到登陸態提供的selector。
  • 第五步:調用該方法- (id)performSelector:(SEL)aSelector withObject:(id)object;完成該服務的調用。

登陸模塊:

/*咱們在登陸模塊建立一個GetLoginCookie類*/
/*.h和.m以下*/
複製代碼
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface GetLoginCookie : NSObject

- (id)getLoginCookieWithObjc:(id)obj;

@end

NS_ASSUME_NONNULL_END
複製代碼
#import "GetLoginCookie.h"

@implementation GetLoginCookie

- (id)getLoginCookieWithObjc:(id)obj {
    return @"cookie";
}

@end
複製代碼

調用模塊:

/*在模塊中獲取到GetLoginCookie的Class*/
Class cookieCls = NSClassFromString(@"GetLoginCookie");

/*經過Class,生成一個GetLoginCookie實例*/
id cookieInstance = [[cookieCls alloc]init];

/*經過方法名生成一個SEL*/
SEL selector = NSSelectorFromString(@"getLoginCookieWithObjc:");

/*調用performSelector並獲取返回值*/
NSString *cookie = [cookieInstance performSelector:selector withObject:@"it's me!"];
NSLog(@"cookie->%@",cookie);

複製代碼

Command + B,完美運行,咱們也獲得了咱們想要的結果。

對比方案一和方案二,方案二的確解決了服務分散很差管理的問題,可是依然存在幾個問題。

  • 依然沒有一個配置的地方讓調用者一下就能看到類名或者sel名,方便進行調用。
  • 還有個問題,咱們很容易發現這兩個方案都是**「去中心化的」**。也就是說,消息的發送和消息的接收處理都是直接點對點的。去中心化帶來了不少問題,若是登陸態的服務出現問題,而咱們又沒有一個統一收口的地方統一處理,不可控。

這就比如區塊鏈技術去中心化雖然帶來了不少技術變革,但一樣也帶來了一些隱患。若是沒有上面的👆的監管,那不少black money💰能夠經過區塊鏈手段洗到國外。想一想不少貪官拿着咱們辛辛苦苦繳納的稅,把貪來的錢都洗到了國外,而後老婆孩子在國外逍遙自在,本身在國內作luo官。而咱們依然活在水深火熱之中,百姓民不聊生,苦不堪言,咱們心裏該是何等氣憤!!!😓。

扯多了,咱們回到正題。😄

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

經過對比前兩個方案,咱們大概對於服務組件應該知足哪些要素有了更加清晰的認識。

  • 服務組件要易於管理,統一分佈在模塊中的某個地方。
  • 服務組件最好經過配置文件去管理,方便業務方查閱調用等。
  • 服務組件去Model化,完全解除、還有支持同步異步調用等。
  • 服務組件最好用中間件方式,有統一收口的地方,發生問題可控。
  • 服務組件最好支持靜態註冊、動態註冊等,擴展性高。

咱們來簡單畫一下,服務組件架構圖。

  • 首先爲了解決服務易於管理問題,咱們這裏使用plist來維護業務服務列表和具體服務名與服務的對應關係。

如圖所示,IQService.plist維護了業務list,通常IQService主工程維護一份便可。

LoginModule.plist中維護了該組件爲外部提供的全部服務列表(服務名和實現類的對應關係)

  • 去model化,咱們這裏用多參數來解決(也能夠經過NSDictionry解決)。
/**
 同步、異步調用

 @param sevice 微服務名
 */
+ (void)invokeMicroService:(NSString *)sevice,...;

/**
 同步調用

 @param service 微服務名
 @return 同步調用返回值
 */
+ (id)invokeMicroServiceSync:(NSString *)service,...;

複製代碼

咱們再來看下具體的使用姿式。

登陸模塊:

首先建立LoginModuleCookieService類,並將該類註冊到LoginModule中。

複製代碼
.h聲明
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LoginModuleCookieService : NSObject

- (NSString *)getCookieWithSignature:(NSString *)signature;

@end

NS_ASSUME_NONNULL_END
複製代碼
.m實現
#import "LoginModuleCookieService.h"

@implementation LoginModuleCookieService

- (NSString *)getCookieWithSignature:(NSString *)signature {
    return [NSString stringWithFormat:@"%@->cookie",signature];
}

@end
複製代碼

調用模塊:

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

分析到如今,方案三基本能知足大部分業務需求。具體實現代碼已經開源到GitHub -----> IQService,一個iOS端模塊間通訊的解決方案。喜歡的筒子能夠來波Star❤️,也歡迎你們提交PR和ISSUE。

騙你們刷完Star,如今再潑盆冷水。。。😅
咱們再仔細思考一下方案三,貌似有幾個問題依然沒有解決

1.編譯時依然沒法進行參數正確性校驗,attribute?宏定義?
2.目前只有靜態註冊,不支持動態註冊。

上面兩個問題,歡迎你們進行頭腦風暴。有好的解決方案能夠留言分享,也能夠提交PRs or Issues。github.com/Lobster-Kin…

在這裏提示一點,沒有一個方案是100%OK的,只有適合本身的纔是最好的。😄

架構和組件化系列文章預告:說說MVVM,會一步步跟你們寫一個輕量的view和viewModel進行數據綁定的框架。

文章首發GitHub github.com/Lobster-Kin…

相關文章
相關標籤/搜索