組件化架構(gòu)缀棍、通信方案想必大家都看到很多朴上,蘑菇街李忠 、casatwy 這兩個(gè)博客收益頗多懂鸵,我先說下開始我的組件通信思路偏螺。
前期我講述了,如何用cocoapods模塊化一個(gè)工程匆光,那么就面臨一個(gè)問題套像。開發(fā)工程師除了能看到基礎(chǔ)模塊和自己寫的模塊之外,看不到別人寫的模塊终息,別人的模塊叫什么名字也不知道夺巩,如何通信呢
我們需要解決三個(gè)問題
- 視圖展示:比如普通的跳轉(zhuǎn)
- 參數(shù)傳遞:比如我們到訂單頁面,至少要給這個(gè)頁面一個(gè)訂單號(hào)
- 回調(diào):一說到回調(diào)周崭,那就是block柳譬、delegate占大多數(shù)了
首先我們解決視圖展示問題
Class class = NSClassFromString(@"CouponViewController");
UIViewController* couponVC = [[class alloc] init];
[self.navigationController pushViewController:couponVC animated:YES];
上面的代碼很容易看懂是我要跳轉(zhuǎn)到CouponViewController頁面
其次我們解決參數(shù)傳遞問題
一提起delegate,原理就簡(jiǎn)單的理解為:比如A跳轉(zhuǎn)到B了续镇,B頁面讓A頁面就調(diào)用一個(gè)方法并傳遞參數(shù)美澳,這里我們正向去考慮 A頁面直接讓B頁面主動(dòng)調(diào)用一個(gè)方法并傳遞參數(shù)這里就以類似delegate的方式實(shí)現(xiàn)了參數(shù)傳遞
回到代碼里
performSelector: withObject:
大家對(duì)上面的方法可能看到的比較多,只要調(diào)用perform開頭的方法摸航,這個(gè)方法就擺在第一位了制跟,今天終于排上用場(chǎng)了。類方法忙厌、實(shí)例方法 都可以調(diào)用的凫岖,滿足各種需求。
比如在A頁面調(diào)整到B頁面逢净,第一步我們想辦法已經(jīng)創(chuàng)建了這個(gè)B對(duì)象了哥放,上面的方法的作用就是讓B去調(diào)用他自己實(shí)現(xiàn)的方法,同時(shí)給傳遞一個(gè)參數(shù)爹土,原理跟delegate一樣
下面是A頁面的代碼甥雕,我們假定有一個(gè)按鈕,點(diǎn)擊后執(zhí)行如下的方法跳轉(zhuǎn)到B頁面并且傳遞一個(gè)參數(shù)
//傳遞參數(shù)
Class class = NSClassFromString(@"AViewController");
UIViewController* BVC = [[class alloc] init];
SEL selector = NSSelectorFromString(@"setCouponFilter:");
if ([BVC respondsToSelector:selector]) {
[BVC performSelector:selector withObject:@{@"name": @"This is a super order"}];
}
[self.navigationController pushViewController: AVC animated:YES];
那么我們?cè)贐頁面實(shí)現(xiàn)一下這個(gè)方法
-(void)setCouponFilter:(NSDictionary *)couponFilter{
NSLog(@"上個(gè)頁面帶來的參數(shù):%@", couponFilter);
}
那么在B頁面就會(huì)看到輸出這個(gè)參數(shù)了胀茵,參數(shù)你拿到了社露,隨便怎么處理了
最后我們解決回調(diào)問題
block的方式詳見代碼,也類似于傳遞一個(gè)參數(shù)琼娘,參數(shù)為block峭弟。
delegate方式如下
可以定義一個(gè)協(xié)議,同時(shí)也要設(shè)置一個(gè)delegate脱拼,我們假定要A頁面稱為B頁面的delegate瞒瘸,那么以前我們會(huì)在B頁面的接口文件.h中聲明一個(gè)代理,現(xiàn)在聲明也沒有用了熄浓,因?yàn)榭床坏角槌簦∧敲慈绾卧O(shè)置呢?delegate也可以當(dāng)做一個(gè)普通的參數(shù)傳遞,比如在A頁面將self,作為參數(shù)傳遞過去
SEL delegateSEL = NSSelectorFromString(@"setDelegate:");
if ([BVC respondsToSelector:delegateSEL]) {
[BVC performSelector:delegateSEL withObject:self];
}
在B頁面,我們就聲明一個(gè)delegate屬性牙丽,把set方法當(dāng)做我們的傳遞參數(shù)方法
-(void)setDelegate:(id<ModuleDelegate>)delegate{
_delegate = delegate;
NSLog(@"delegate::%@", delegate);
}
在跳轉(zhuǎn)到B頁面的時(shí)候,會(huì)看到有delegate輸出了肥败,這樣delegate也有了,我們就可以像普通的delegate方式去使用了
- 定義一個(gè)協(xié)議劈猿,大家都能看到的協(xié)議
- 為B頁面設(shè)置一個(gè)delegate拙吉,目前就A頁面
- 在A頁面中實(shí)現(xiàn)協(xié)議的方法潮孽,其實(shí)就是B讓A調(diào)用的方法
公共協(xié)議部分
@protocol ModuleDelegate <NSObject>
@optional
-(void)module:(id)obj info:(id)info;
B頁面某個(gè)操作后調(diào)用了這個(gè)方法
if (self.delegate && [self.delegate respondsToSelector:@selector(module:info:)]) {
[self.delegate module:self.class.description info:@{@"type":@"super coupon", @"desc": @"可狠了", @"fee":@50}];
}
[self.navigationController popViewControllerAnimated:YES];
在A頁面中
-(void)module:(id)obj info:(id)info{
NSLog(@"module: %@ info: %@", obj, info);
}
這樣通信方式的基本原理就完事了揪荣。
這里的類名、方法名都屬于硬編碼往史,參數(shù)用的字典仗颈,為了去model化,就必須要求去維護(hù)一個(gè)Protocol椎例,這里我定義了一個(gè)公共的Protocol
//
// ModuleProtocol.h
// ModuleCommuication
//
// Created by L's on 2017/1/23.
// Copyright ? 2017年 zuiye. All rights reserved.
//
#ifndef ModuleProtocol_h
#define ModuleProtocol_h
/**
通信方式示例
Class class = NSClassFromString(@"CouponViewController");
UIViewController* couponVC = [[class alloc] init];
//設(shè)置delegate
SEL delegateSEL = NSSelectorFromString(@"setDelegate:");
if ([couponVC respondsToSelector:delegateSEL]) {
SuppressPerformSelectorLeakWarning(
[couponVC performSelector:delegateSEL withObject:self];
);
}
// 設(shè)置block
void (^block)(id) = ^(id couponObj){
NSLog(@"通過block回調(diào):%@", couponObj);
};
SEL blockSEL = NSSelectorFromString(@"setBlock:");
if ([couponVC respondsToSelector:blockSEL]) {
SuppressPerformSelectorLeakWarning(
[couponVC performSelector:blockSEL withObject:block];
);
}
//傳遞參數(shù)
SEL selector = NSSelectorFromString(@"setCouponFilter:");
if ([couponVC respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[couponVC performSelector:selector withObject:@{@"name": @"This is a super order"}];
);
}
[self.navigationController pushViewController:couponVC animated:YES];
*/
/** 解決編譯的時(shí)候出錯(cuò)的問題 */
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
/**
通信協(xié)議挨决,所有頁面的接口都在這個(gè)公共的協(xié)議方法里定義
*/
@protocol ModuleDelegate <NSObject>
@optional
/**
對(duì)外提供接口的模塊必須實(shí)現(xiàn)delegate設(shè)置的方法,當(dāng)然方法叫什么名字無所謂 內(nèi)部如何寫無所謂(你問我為啥叫setDelegate订歪,就寫方法的時(shí)候少敲幾個(gè)字母脖祈,復(fù)用屬性的set方法了),主要是要拿到delegate
@param delegate 稱為其delegate的對(duì)象
*/
-(void)setDelegate:(id<ModuleDelegate>)delegate;
/*******************優(yōu)惠券頁面通信協(xié)議***********************/
/**
設(shè)置接口參數(shù)
給優(yōu)惠券頁面?zhèn)鬟f參數(shù)刷晋,方法名字無所謂(你問我為啥叫setCouponFilter盖高,就寫方法的時(shí)候少敲幾個(gè)字母,復(fù)用屬性的set方法了)眼虱,重點(diǎn)在obj這個(gè)參數(shù)上了喻奥,模塊內(nèi)部如何處理這里不去管,這里直接實(shí)現(xiàn)了屬性的set方法
接口里的屬性建議放到.h接口文件中捏悬,便于模塊內(nèi)部自己維護(hù)
@param obj 提供給模塊的參數(shù)
*/
-(void)setCouponFilter:(id)obj;
/**
優(yōu)惠券頁面回調(diào)
@param obj 預(yù)留撞蚕,傳遞什么無所謂,目前傳遞了本身过牙,便于外部查看
@param info 回調(diào)的參數(shù)
*/
-(void)module:(id)obj info:(id)info;
/*******************優(yōu)惠券頁面通信協(xié)議***********************/
@end
#endif /* ModuleProtocol_h */
還沒完事呢甥厦,大家會(huì)發(fā)現(xiàn)一個(gè)問題,就是如果執(zhí)行下面的方法會(huì)有警告啊
performSelector: withObject:
可以這樣解決
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[someController performSelector: NSSelectorFromString(@"someMethod")]
#pragma clang diagnostic pop
為了解決這個(gè)問題我們定義一個(gè)宏
/** 解決編譯的時(shí)候出錯(cuò)的問題 */
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
在調(diào)用的時(shí)候
//傳遞參數(shù)
SEL selector = NSSelectorFromString(@"setCouponFilter:");
if ([BVC respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[BVC performSelector:selector withObject:@{@"name": @"This is a super order"}];
);
}
其他處理
這里我們集中在控制器處理上了寇钉,現(xiàn)實(shí)中夸組件調(diào)用各種各樣了刀疙,比如我要從 UserModel 獲取用戶信息,在UserModel 實(shí)現(xiàn)中有如下方法
-(NSDictionary*)getUserInfo{
NSDictionary* info = @{@"userId": @"8888", @"name": @"張三", @"age": @18};
return info;
}
那么我們?cè)谕獠空{(diào)用就可以獲取到信息
Class class = NSClassFromString(@"UserModel");
SEL selector = NSSelectorFromString(@"getUserInfo");
id target = [[class alloc] init];
if (target && [target respondsToSelector:selector]) {
id result;
SuppressPerformSelectorLeakWarning(
result = [target performSelector:selector]
);
NSLog(@"用戶信息:%@", result);
}
如果需要調(diào)用工具類Utils里的方法處理數(shù)據(jù)摧莽,比如工具類Utils有個(gè)方法是給字典里的字符串追加前綴
+(NSDictionary *)formatInfo:(NSDictionary *)info{
if (![info isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
for (id key in info) {
dic[key] = [NSString stringWithFormat:@"zuiye_%@", info[key]];
}
return dic;
}
那么實(shí)際調(diào)用的時(shí)候
NSDictionary* info = @{@"userId": @"8888", @"name": @"張三", @"age": @18};
Class class = NSClassFromString(@"Utils");
SEL selector = NSSelectorFromString(@"formatInfo:");
if ([class respondsToSelector:selector]) {
id result;
SuppressPerformSelectorLeakWarning(
result = [class performSelector:selector withObject:info]
);
NSLog(@"用戶信息追加前綴:%@", result);
}
總結(jié)了下我的方法有點(diǎn)類似于 Target-Action + 協(xié)議 的方式庙洼,當(dāng)然這個(gè)只是簡(jiǎn)單的實(shí)現(xiàn),如果復(fù)雜的功能,那么還有去封裝一下油够,比如原生蚁袭、H5切換,我的思路不一定是最好的石咬,如果想看看大神們的通信方案揩悄,開篇提供了鏈接,大家可以去看看鬼悠。
感謝您閱讀完畢删性,如有疑問,歡迎添加QQ:714387953(蝸牛上高速)焕窝。
github:https://github.com/yhl714387953/ModuleCommuication
如果有錯(cuò)誤蹬挺,歡迎指正,一起切磋它掂,共同進(jìn)步
如果喜歡可以Follow巴帮、Star、Fork虐秋,都是給我最大的鼓勵(lì)榕茧。