隨着項目的發展,不少規模較大的App,例如淘寶、美團之類的App,已經變成了多個App的大集合。有衆多的業務線和不一樣的開發團隊,分別負責開發各自的部分(模塊)。相互之間須要調用(通訊)的時候,若是直接引用並調用,就會變成這樣。json
能夠看出,變如今工程中就是一個工程中,多個模塊之間的相互import,耦合在一塊兒。bash
1. 全部模塊在一個工程裏,沒法分開編譯,開發時每次編譯,須要編譯所有源碼,嚴重影響開發效率。app
2. 因爲全部模塊耦合在一塊兒,開發A模塊功能時,可能須要動到B模塊裏面的代碼。可能會致使B模塊的問題。ide
3. 若是淘寶App裏面的C模塊,想要挪到天貓App裏面,幾乎確定是要在天貓App中重寫一遍C模塊的代碼了,由於C模塊耦合在淘寶App中根本拆不出來。之後C模塊的需求開發都要分別在兩個App中開發一次。函數
1. 每一個模塊分開編譯,互不影響。ui
2. 每一個模塊分紅不一樣的倉庫,不一樣團隊進行維護。spa
3. 整個模塊能夠輕鬆的複用。3d
首先看下BeeHive給出的結果,demo中的解耦結果是這樣:調試
#import "HomeServiceProtocol.h"
id<HomeServiceProtocol> homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
// 盡情操做 homeVc,調用 協議HomeServiceProtocol中提供的方法。複製代碼
createService:方法,只要傳進去一個參數protocol,就能建立出來一個homeVCcode
顯然,HomeServiceProtocol和homeVC確定提早偷偷註冊進一個字典裏了。key是HomeServiceProtocol,value是homeVC。而後給一個protocol就能找到對應的對象。否則不可能根據協議找到實現的。(除非你遍歷全部類,扯遠了)
homeVC遵照HomeServiceProtocol,因此能夠在下面隨意調用HomeServiceProtocol中提供的方法,即其餘模塊須要調用homeVC的全部方法,全都暴露在這個協議中,供其餘模塊調用。
(這樣作就只須要依賴抽象的protocol,而不須要依賴class了)
我在個人模塊裏調用homeVC,只要protocol中定義了方法,就能調用,並編譯經過。進行模塊的發版。
從上面的猜想中可知,有一個全局的字典註冊了全部protocol與class的對應關係,使後來能夠在工程的任意地方都能根據protocol建立出來具體的class,並調用protocol裏全部的方法。
看一下註冊方式和讀取時機。
BeeHiveService的宏定義:
#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";複製代碼
展開,將參數帶進去以後
@class BeeHive;
char * kHomeServiceProtocol_service BeeHiveDATA(BeehiveServices) = "{ ""HomeServiceProtocol"" : ""BHViewController""}";複製代碼
BeeHiveDATA的宏定義:
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))複製代碼
徹底展開帶進去以後,獲得
@class BeeHive;
char * kHomeServiceProtocol_service __attribute((used, section("__DATA,"BeehiveServices" "))) = "{ ""HomeServiceProtocol"" : ""BHViewController""}";複製代碼
上面的意思是,會將等號後面的字符串,{ ""HomeServiceProtocol"" : ""BHViewController""}
,存儲到mach-o文件的__DATA段的一個名叫BeehiveServices的section中。
在BHAnnotation.m 中(main函數以前)
__attribute__((constructor))
void initProphet() {
_dyld_register_func_for_add_image(dyld_callback);
}複製代碼
首先,__attribute__((constructor))修飾的函數,會在main函數以前被調用。
_dyld_register_func_for_add_image函數,dyld加載每一個庫的時候,都會調用其中的回調block,即dyld_callback
static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
//register services
// 這裏是讀取以前註冊在BeehiveServices的section中的字符串
NSArray<NSString *> *services = BHReadConfiguration("BeehiveServices",mhp);
for (NSString *map in services) {
NSData *jsonData = [map dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!error) {
if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
// 這裏解析出來 protocol 和對應的class
NSString *protocol = [json allKeys][0];
NSString *clsName = [json allValues][0];
if (protocol && clsName) {
// 註冊進全局的BHServiceManager的一個字典裏
[[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
}
}
}
}
}複製代碼
出去出來後轉換成鍵值對,存放在[BHServiceManager shareManager].allServiceDict的字典中。
整個流程圖:
放進一個plist文件中來維護。
application:didFinishLauchingWithOptions:(main函數以後)
- (void)registerLocalServices
{
NSString *serviceConfigName = [BHContext shareInstance].serviceConfigName;
NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist"];
if (!plistPath) {
return;
}
NSArray *serviceList = [[NSArray alloc] initWithContentsOfFile:plistPath];
[self.lock lock];
for (NSDictionary *dict in serviceList) {
NSString *protocolKey = [dict objectForKey:@"service"];
NSString *protocolImplClass = [dict objectForKey:@"impl"];
if (protocolKey.length > 0 && protocolImplClass.length > 0) {
[self.allServicesDict addEntriesFromDictionary:@{protocolKey:protocolImplClass}];
}
}
[self.lock unlock];
}複製代碼
經過上述方式,結構就變成了下圖:
ABCD之間相互依賴被解除,若是是模塊A的開發者,開發時只須要編譯模塊A,能夠選擇性編譯模塊BCD,不影響運行調試。