前言
使用BeeHive來進行項目組件化卓舵,其實是使用BeeHive來構建一個中間層均蜜,通過中間層來解耦各個模塊笤休。在文章iOS組件化通用工具淺析有簡單介紹過BeeHive的一些組件化思路齿椅,本文將更多的從使用者的角度來分析BeeHive。
1. 用法
通過構建中間層來組件化項目玻侥,共需要三步:
- 創(chuàng)建protocol
- 創(chuàng)建impClass
- 存儲protocol-impClass映射關系
1.1. 創(chuàng)建protocol
protocol表示模塊對外暴露的接口决摧,調(diào)用模塊時只需要依賴模塊對應的protocol,就可以實現(xiàn)對模塊的調(diào)用凑兰。
下列代碼表示掌桩,模塊A對應的協(xié)議BHServiceProtocol
的定義,調(diào)用者可以通過-[getModuleAMainViewController]
和-[pushToModuleAOneViewController]
這兩個方法來調(diào)用模塊A票摇。
// BHServiceProtocol.m
#import "BHServiceProtocol.h"
#import <Foundation/Foundation.h>
@protocol ModuleAServiceProtocol <NSObject, BHServiceProtocol>
- (UIViewController *)getModuleAMainViewController;
- (void)pushToModuleAOneViewController;
@end
這個協(xié)議需要繼承BeeHive中的協(xié)議BHServiceProtocol
拘鞋,協(xié)議BHServiceProtocol
中定義如下兩個可選方法+[singleton:]
和+[shareInstance]
砚蓬。
如果協(xié)議對應的響應者impClass實現(xiàn)了這兩個方法矢门,并且+[singleton:]
方法返回YES
,則調(diào)用響應類的+[shareInstance]
方法來創(chuàng)建響應者對象。否則祟剔,直接調(diào)用[[implClass alloc] init]
來創(chuàng)建對象隔躲。
#import <Foundation/Foundation.h>
#import "BHAnnotation.h"
@protocol BHServiceProtocol <NSObject>
@optional
+ (BOOL)singleton;
+ (id)shareInstance;
@end
1.2. 創(chuàng)建impClass
impClass是protocol對應的響應類,它需要遵守這個protocol協(xié)議物延,它可以是模塊中一個已經(jīng)存在的業(yè)務類宣旱,也可以是這個模塊的一個封裝類。
如果模塊對外暴露的方法全部來自于同一個業(yè)務類叛薯,則可以將這個業(yè)務類設置成impClass浑吟;
如果模塊對外暴露的方法全部來自于多個不同的業(yè)務類,則需要給這個模塊創(chuàng)建一個封裝類耗溜,通過這個封裝類來實現(xiàn)對模塊的調(diào)用组力,impClass指向這個封裝類。(這種方式也叫做target-action)
第一種方式比較常用抖拴,BeeHive的官方demo基本上是使用的這種方法燎字。
模塊A的impClass是ModuleAService
類,它是一個封裝類阿宅,內(nèi)部實現(xiàn)了對模塊A中兩個不同類的調(diào)用候衍。
//ModuleAService.m
#import "ModuleAOneViewController.h"
#import "ModuleAViewController.h"
#import "ModuleAService.h"
@implementation ModuleAService
- (UIViewController *)getModuleAMainViewController{
return [ModuleAViewController new];
}
- (void )pushToModuleAOneViewController{
UITabBarController *tab = (UITabBarController *)[UIApplication sharedApplication].delegate.window.rootViewController;
UINavigationController *nav = tab.selectedViewController;
ModuleAOneViewController *one = [ModuleAOneViewController new];
[nav pushViewController:one animated:YES];
}
另外,模塊C對外暴露的方法只有一個洒放,所以模塊C使用的是第一種方式蛉鹿,它的impClass直接指向ModuleCViewController
這個業(yè)務類。
1.3. 設置protocol-impClass映射關系
在BeeHive中拉馋,所有protocol-impClass的映射關系都由BHServiceManager
管理榨为,BHServiceManager
主要提供了兩個方法:
- (void)registerService:(Protocol *)service implClass:(Class)implClass;
- (id)createService:(Protocol *)service;
方法名中的service指的就是上文中所說的protocol,所以方法一的作用是注冊protocol-impClass的映射關系煌茴,方法二的作用是通過protocol獲取對應的響應類随闺。
在BHServiceManager
類中,有一個叫做allServicesDict
的屬性蔓腐,它保存了所有的protocol-impClass的映射關系矩乐,上述方法一和方法二就是根據(jù)這個屬性來執(zhí)行的。
allServicesDict
是一個可變字典回论,其中key是protocol的字符串名稱散罕,value是impClass的字符串名稱。
具體注冊方式有下列三種
1. 使用BeeHive
類的-[registerService:service:]
方法-[registerService:service:]
的實現(xiàn)
- (void)registerService:(Protocol *)proto service:(Class) serviceClass
{
[[BHServiceManager sharedManager] registerService:proto implClass:serviceClass];
}
這個方法內(nèi)部就是調(diào)用了BHServiceManager
的-[registerService:implClass:]
方法傀蓉,將傳入的protocol
和impClass
添加到BHServiceManager
類的屬性allServicesDict
中欧漱。
2. 使用宏BeeHiveService
上文中,定義了ModuleA模塊的協(xié)議ModuleAServiceProtocol
和響應類ModuleAService
葬燎,可以使用如下代碼來注冊它們之間的關系:
BeeHiveService(ModuleAServiceProtocol, ModuleAService)
使用宏來注冊時误甚,務必在本模塊中調(diào)用宏缚甩。如果在主工程中調(diào)用,且主工程沒有導入這個模塊(更準確的說是impClass對應的類沒有導入)窑邦,會導致程序crash擅威。
在上一篇文章第四節(jié)中已經(jīng)講過了注冊Module
類的宏BeeHiveMod
,這兩個宏的實現(xiàn)原理是一樣的冈钦,都是在mach-o文件中增加一個section來存儲數(shù)據(jù)郊丛,然后在啟動項目時取出數(shù)據(jù),最終也是調(diào)用BHServiceManager
的-[registerService:implClass:]
方法來注冊瞧筛,詳細過程這里就不在贅述厉熟。
mach-o文件的section:__DATA:BeehiveServices
中存儲的是一個json格式的字符串:
"{ \"ModuleAServiceProtocol\" : \"ModuleAService\"}"
3. 使用plist文件
使用plist文件注冊,需要在初始化BeeHive時指定plist文件的路徑
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";
plist文件的格式:
需要注意的是BeeHive.bundle必須添加到項目的主工程的target上较幌,因為BeeHive內(nèi)部是在[NSBundle mainBundle]
的目錄下尋找BeeHive.bundle庆猫。
當使用cocoapods來加載BeeHive時,默認情況下绅络,BeeHive.bundle是存在于BeeHive.framework中月培,這個時候使用[NSBundle mainBundle]
時獲取不到BeeHive.bundle的,解決辦法是改用[NSBundle bundleForClass:self.class]
或將BeeHive.bundle添加到項目的主工程的target上恩急。
2. 使用場景
一個典型的場景杉畜,當調(diào)用模塊A時,如果當前還沒有登錄衷恭,則調(diào)用登錄模塊此叠,登錄成功之后,再調(diào)用模塊A随珠;如果已經(jīng)登錄了灭袁,則直接調(diào)用模塊A。
以項目BeeHive-demo3為例
模塊A對外的協(xié)議ModuleAServiceProtocol
//BHServiceProtocol.h
#import "BHServiceProtocol.h"
#import <Foundation/Foundation.h>
@protocol ModuleAServiceProtocol <NSObject, BHServiceProtocol>
- (void)pushToModuleAViewController;
@end
模塊A的響應類
//ModuleAService.m
#import "ModuleAViewController.h"
#import "ModuleAService.h"
@BeeHiveService(ModuleAServiceProtocol, ModuleAService)
@implementation ModuleAService
- (void )pushToModuleAViewController{
id<LoginServiceProtocol> moduleAService = [[BeeHive shareInstance] createService:@protocol(LoginServiceProtocol)];
[moduleAService loginIfNeedWithCompleteBlock:^(BOOL succeed) {
if (succeed) {
UINavigationController *root = (UINavigationController *)[UIApplication sharedApplication].delegate.window.rootViewController;
ModuleAViewController *moduleA = [ModuleAViewController new];
[root pushViewController:moduleA animated:YES];
}
}];
}
@end
不管有沒有登錄窗看,首先調(diào)用登錄模塊茸歧,具體的跳轉邏輯被保存在block中,然后傳給登錄模塊显沈,登錄完成之后软瞎,執(zhí)行這個block。
登錄模塊的協(xié)議LoginServiceProtocol
//LoginServiceProtocol.h
#import "BHServiceProtocol.h"
#import <Foundation/Foundation.h>
@protocol LoginServiceProtocol <NSObject, BHServiceProtocol>
- (void)loginIfNeedWithCompleteBlock:(void (^)(BOOL))completeBlock;
@end
登錄模塊的響應類
//LoginService.m
#import "LoginViewController.h"
#import "LoginService.h"
@BeeHiveService(LoginServiceProtocol, LoginService)
@implementation LoginService
- (void)loginIfNeedWithCompleteBlock:(void (^)(BOOL))completeBlock{
if ([LoginViewController isLogined]) {
completeBlock(YES);
}else{
LoginViewController *login = [LoginViewController new];
login.completeBlock = completeBlock;
UIViewController *root = [UIApplication sharedApplication].delegate.window.rootViewController;
[root presentViewController:login animated:YES completion:nil];
}
}
@end
如果已經(jīng)登錄拉讯,直接執(zhí)行傳入的block涤浇;如果沒有登錄,則彈出登錄界面魔慷,登錄成功之后只锭,執(zhí)行block。
3. impClass的生命周期
通過上文可知院尔,impClass的對象是最終是由BHServiceManager
類創(chuàng)建的蜻展,但是BHServiceManager
類并沒有持有impClass的對象页滚,本質上,BHServiceManager
相當于是一個對象工廠铺呵。
如果impClass是一個模塊的封裝類,impClass的對象只在當前作用域有效隧熙,超過了這個作用域片挂,這個對象會被釋放掉。
如果impClass是一個模塊的業(yè)務類贞盯,則impClass對象的生命周期依賴于模塊內(nèi)部的具體實現(xiàn)了音念。
如果想長期持有這個impClass對象,通常有兩種方式:
1.在模塊調(diào)用處躏敢,強引用被創(chuàng)建的impClass對象闷愤。
2.實現(xiàn)BeeHive中BHServiceProtocol
協(xié)議的+[singleton]
方法,并返回YES件余。這樣讥脐,被創(chuàng)建的impClass對象會被保存在單例[BHContext shareInstance]
中。(如果同時實現(xiàn)了+[shareInstance]
方法啼器,則使用這個方法來創(chuàng)建impClass的對象)
可以使用下列BHContext
的方法來移除保存的impClass對象
- (void)removeServiceWithServiceName:(NSString *)serviceName;
4. 異常處理
BeeHive可以通過下列設置來開啟異常模式旬渠,在這個模式下,如果遇到BeeHive內(nèi)部的一些錯誤端壳,會直接拋出異常告丢。一般在調(diào)試模式下,應該開啟损谦。生產(chǎn)模式下岖免,應該關閉。
[BeeHive shareInstance].enableException = YES;
[[BeeHive shareInstance] setContext:[BHContext shareInstance]];
4.1 注冊時異常
注冊方式共有三種:
- 使用
BeeHive
類的-[registerService:service:]
- 使用宏
BeeHiveService
- 使用plist文件
注冊時照捡,可能存在下列三種情況:
- protocol和impClass對應的協(xié)議或類不存在
- protocol和impClass存在颅湘,但impClass沒有遵循對應的protocol
- protocol和impClass存在,且impClass遵循對應的protocol
方式一 | 方式二 | 方式三 | |
---|---|---|---|
情況一 | 編譯時報錯 | 啟動時crash | 注冊成功 |
情況二 | 注冊不成功栗精,如果是異常模式栅炒,則crash | 注冊不成功,如果是異常模式术羔,則crash | 注冊成功 |
情況二 | 注冊成功 | 注冊成功 | 注冊成功 |
當注冊方法和被注冊的模塊沒有寫在一起時赢赊,刪除了模塊,而它的注冊方法沒有被刪除级历,這個時候就會出現(xiàn)情況一释移,比如在pod中解除了對模塊的依賴。
要避免情況一中的兩個報錯寥殖,最好是將注冊方法寫在本模塊中玩讳,比如Module類的-[modInit:]
方法中涩蜘,這樣刪除模塊的時候,也刪除了對應的注冊方法熏纯。
不管plist文件中protocol和impClass是否存在同诫,是否匹配,只要它們的key符合格式樟澜,就會被注冊成功误窖。
4.2. 調(diào)用時異常
在調(diào)用模塊時,首先需要創(chuàng)建impClass秩贰,一般是通過BeeHive
類的-[createService:]
方法霹俺,這個方法需要一個protocol
- (id)createService:(Protocol *)proto;
創(chuàng)建好impClass的對象之后,然后這個對象調(diào)用protocol中聲明的方法毒费。
在這個調(diào)用過程中丙唧,可能會遇到下列三種情況:
protocol未注冊 | protocol已注冊,但對應impClass的類不存在 | protocol已注冊觅玻,且對應impClass的類存在想际,但執(zhí)行的方法沒實現(xiàn) | |
---|---|---|---|
處理結果 | 將impClass的值設置為nil,如果是異常模式溪厘,則crash沼琉。 | 將impClass的值設置為nil | 拋出異常 |
4.3. 小結
在調(diào)試階段時,可以開啟異常模式桩匪,這樣就能檢測一些潛在的問題出來打瘪,比如impClass沒有遵循protocol、使用未注冊的protocol來創(chuàng)建impClass傻昙。
關于異常處理闺骚,需要注意的是,impClass必須實現(xiàn)被調(diào)用的方法妆档。另外僻爽,將注冊方法寫在本模塊中。