使用cocoapods創(chuàng)建自己的組件
命令行執(zhí)行pod lib creat 組件名
命令,創(chuàng)建自己的組件唐责。然后就會(huì)讓你輸入一系列的配置:
What is your name?
What is your email?
What platform do you want to use?? [ iOS / macOS ]
> ios
What language do you want to use?? [ Swift / ObjC ]
> objc
Would you like to include a demo application with your library? [ Yes / No ]
> yes
Which testing frameworks will you use? [ Specta / Kiwi / None ]
> none
Would you like to do view based testing? [ Yes / No ]
> no
What is your class prefix?
> ZF
配置完成后御蒲,會(huì)自動(dòng)創(chuàng)建module以及測試的工程。創(chuàng)建的module目錄結(jié)構(gòu)如下:
Class目錄下放置module代碼,Assets目錄下放置module的資源文件间唉。Example目下是為我們創(chuàng)建的測試工程,在測試工程中引入了我們創(chuàng)建的module利术,來進(jìn)行測試呈野。然后我們將module相關(guān)的代碼放到Classes目錄下,切換到Example目錄下執(zhí)行pod install印叁,然后example工程會(huì)更新module代碼被冒,對(duì)module進(jìn)行測試使用。
如果我們創(chuàng)建的module依賴于其他的三方的module或者自己的私有module轮蜕,那么就應(yīng)該在我們創(chuàng)建的module工程的podspec文件配置依賴昨悼。例如依賴
AFNetworking
,Masonry
跃洛,ZFTestModel
率触,配置依賴和頭文件如下:
s.dependency 'AFNetworking'
s.dependency 'Masonry'
s.dependency 'ZFTestModel'
s.prefix_header_contents = '#import "Masonry.h"','#import "UIKit+AFNetworking.h"','#import "LGTest.h"'
這樣配置完成后還沒有結(jié)束,因?yàn)閆FTestModel是我們私有的module汇竭,cocoapods是無法直接通過spec找到的葱蝗,需要手動(dòng)配置我們的私有module查找路徑,在example工程的Podfile文件中指定:
pod 'ZFTestModule', :path => '../../ZFTestModule'
這樣配置完成后就可以在example工程中測試我們的module了细燎,執(zhí)行pod install两曼,然后就可以正常使用了。
注意點(diǎn):加載圖片或者json的時(shí)候玻驻,首先我們需要把圖片或者json文件放到我們上面提到的Assets文件中悼凑。然后在podspec中配置資源的查找路徑:
s.resource_bundles = {
'LGModuleTest' => ['LGModuleTest/Assets/*']
}
配置然后在加載資源(圖片、plist文件璧瞬、xib等)的時(shí)候需要制定bundle佛析。例如加載圖片:
NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:@"/LGModuleTest.bundle"];
NSBundle *resoure_bundle = [NSBundle bundleWithPath:bundlePath];
self.imageView.image = [UIImage imageNamed:@"share_wechat" inBundle:resoure_bundle compatibleWithTraitCollection:nil];
然后重新pod installl,執(zhí)行example工程彪蓬,就可以加載資源了寸莫。
CTMediator方式進(jìn)行模塊間通信
使用CTMediator可以在模塊間通過scheme的形式進(jìn)行通信,這樣就可以實(shí)現(xiàn)解耦合档冬。
scheme://[target]/[action]?[params]
url sample:
aaa://targetA/actionB?id=1234
1膘茎、處理scheme桃纯,將scheme中的target、action和params分離出來披坏;
2态坦、使用runtime生成對(duì)應(yīng)的target對(duì)象,action生成SEL;
3棒拂、如果返回值為基本數(shù)據(jù)類型和void使用NSInvocation實(shí)現(xiàn)對(duì)Target的SEL的調(diào)用伞梯,否則使用performSelector
執(zhí)行對(duì)應(yīng)的SEL。
CTMediator屬于底層的基礎(chǔ)層帚屉,但是我們A和B模塊通信谜诫,比如A模塊要跳轉(zhuǎn)到B模塊的DetailViewController,需要引入DetailViewController攻旦,然后創(chuàng)建該視圖控制器進(jìn)行跳轉(zhuǎn)喻旷。或者A模塊需要對(duì)B模塊的一個(gè)視圖進(jìn)行賦值牢屋,也需要拿到B模塊的視圖且预,然后取出來參數(shù)進(jìn)行賦值。這些代碼不能包含在CTMediator層烙无,因?yàn)樗腔A(chǔ)模塊锋谐,不能包含業(yè)務(wù)代碼,不能對(duì)上層的業(yè)務(wù)代碼產(chǎn)生依賴截酷。那么此時(shí)就會(huì)有一個(gè)中間層Target-Action層涮拗。Target-Action是跟隨著業(yè)務(wù)模塊B進(jìn)行維護(hù)的,相當(dāng)于模塊B對(duì)外暴露的功能接口通過自己的Target-Action來提供合搅。
例如下面的TargetA
- (id)Action_configCell:(NSDictionary *)params
{
NSString *title = params[@"title"];
NSIndexPath *indexPath = params[@"indexPath"];
UITableViewCell *cell = params[@"cell"];
// 這里的TableViewCell的類型可以是自定義的多搀,我這邊偷懶就不自定義了。
cell.textLabel.text = [NSString stringWithFormat:@"%@,%ld", title, (long)indexPath.row];
return nil;
}
- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params
{
// 因?yàn)閍ction是從屬于ModuleA的灾部,所以action直接可以使用ModuleA里的所有聲明
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
viewController.valueLabel.text = params[@"key"];
return viewController;
}
那么我們?cè)趺词褂肅TMediator進(jìn)行模塊間通信呢康铭?直接使用CTMediator,然后拼接scheme來進(jìn)行通信當(dāng)然也可以赌髓。但是感覺沒有那么友好从藤,使用過程中也容易出錯(cuò)。那么我們可以對(duì)CTMediator進(jìn)行一次封裝锁蠕,通過給CTMediator增加Category的方式夷野,來實(shí)現(xiàn)封裝好每個(gè)Target-Action提供的功能。這樣我們調(diào)用的時(shí)候荣倾,直接調(diào)用我們自己封裝的這一層Category的方法悯搔,傳遞進(jìn)來相關(guān)的參數(shù),就可以實(shí)現(xiàn)通信了舌仍。這個(gè)Category可以封裝成獨(dú)立的一個(gè)module妒貌,跟隨著業(yè)務(wù)進(jìn)行維護(hù)通危。
例如下面為#import "CTMediator+CTMediatorModuleAActions.h"
的內(nèi)容:
NSString * const kCTMediatorTargetA = @"A";
NSString * const kCTMediatorActionNativeFetchDetailViewController = @"nativeFetchDetailViewController";
@implementation CTMediator (CTMediatorModuleAActions)
- (UIViewController *)CTMediator_viewControllerForDetail
{
// 此處的performTarget方法為CTMediator的方法。
UIViewController *viewController = [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativeFetchDetailViewController
params:@{@"key":@"value"}
shouldCacheTarget:NO
];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之后灌曙,可以由外界選擇是push還是present
return viewController;
} else {
// 這里處理異常場景菊碟,具體如何處理取決于產(chǎn)品
return [[UIViewController alloc] init];
}
}
@end
使用CTMediator進(jìn)行模塊間通信的流程圖如下:
BeeHive方式進(jìn)行模塊間通信
一到多的通信:
系統(tǒng)事件的分發(fā):比如Application的相關(guān)事件,包括啟動(dòng)在刺、進(jìn)入前臺(tái)逆害、進(jìn)入后臺(tái)等事件。按照我們一般的方式蚣驼,如果我們需要在app進(jìn)入前臺(tái)的時(shí)候做一些事情魄幕,就得在AppDelegate中寫一些代碼。但是項(xiàng)目組件化后隙姿,如果在AppDelegate的代理方法中寫入各個(gè)module的代碼梅垄,耦合性也是比較強(qiáng)的厂捞。那么我們可以將這部分app生命周期的相關(guān)方法進(jìn)行下沉输玷,然后其他的模塊就可以直接獲取到app生命周期的回調(diào)了。整個(gè)過程如下:
- 1靡馁、AppDelegate集成與BHAppDelegate欲鹏,那么App的生命周期都會(huì)被下層的BHAppDelegate做攔截;
- 2臭墨、AppDelegate各個(gè)需要提供給其他模塊的方法封裝成protocol的形式來提供給外面赔嚎;
- 3、在需要獲取到App生命周期的模塊中胧弛,通過BHModuleManager進(jìn)行注冊(cè)成為觀察者尤误,同時(shí)遵守protocol協(xié)議,實(shí)現(xiàn)協(xié)議方法结缚。BHModuleManager會(huì)綁定一個(gè)Context损晤,context中保存了App的一些全局的信息;
- 4红竭、BHAppDelegate在對(duì)應(yīng)的事件發(fā)生的時(shí)候通知遵守協(xié)議的注冊(cè)者尤勋。
這樣的話,其他的module茵宪,想要在app的生命周期方法中做些事情最冰,只需要通過BHModuleManager注冊(cè)就可以了。而不需要直接和AppDelegate進(jìn)行耦合稀火。
點(diǎn)到點(diǎn)的通信:
上面介紹了BeeHive一對(duì)多的通信方式暖哨,下面我們介紹下一對(duì)一的通信方式:
一對(duì)一的通信方式是通過協(xié)議和類對(duì)象綁定的方式。通過注冊(cè)的方式將Class和protocol綁定在一起凰狞,也就是將Class和protocol放到Dictionay中以鍵值對(duì)的方式存起來篇裁,然后想要獲取到Class箕慧,只需要拿到protocol,然后到Dictionay中就能拿到對(duì)應(yīng)Class對(duì)象茴恰。Class對(duì)象可以執(zhí)行protocol中的方法颠焦。
注冊(cè)的方式分為兩種:一種是動(dòng)態(tài)的注冊(cè);一種為靜態(tài)注冊(cè)往枣。動(dòng)態(tài)注冊(cè)是在Class文件中使用宏聲明該類為模塊入口伐庭。靜態(tài)注冊(cè)是將Class和protocol寫入plist文件中,然后應(yīng)用啟動(dòng)的時(shí)候回去加載plist文件進(jìn)行注冊(cè)分冈。