組件化架構、通信方案想必大家都看到很多,蘑菇街李忠 、casatwy 這兩個博客收益頗多,我先說下開始我的組件通信思路。
前期我講述了,如何用cocoapods模塊化一個工程,那么就面臨一個問題。開發工程師除了能看到基礎模塊和自己寫的模塊之外,看不到別人寫的模塊,別人的模塊叫什么名字也不知道,如何通信呢
我們需要解決三個問題
- 視圖展示:比如普通的跳轉
- 參數傳遞:比如我們到訂單頁面,至少要給這個頁面一個訂單號
- 回調:一說到回調,那就是block、delegate占大多數了
首先我們解決視圖展示問題
Class class = NSClassFromString(@"CouponViewController");
UIViewController* couponVC = [[class alloc] init];
[self.navigationController pushViewController:couponVC animated:YES];
上面的代碼很容易看懂是我要跳轉到CouponViewController頁面
其次我們解決參數傳遞問題
一提起delegate,原理就簡單的理解為:比如A跳轉到B了,B頁面讓A頁面就調用一個方法并傳遞參數,這里我們正向去考慮 A頁面直接讓B頁面主動調用一個方法并傳遞參數這里就以類似delegate的方式實現了參數傳遞
回到代碼里
performSelector: withObject:
大家對上面的方法可能看到的比較多,只要調用perform開頭的方法,這個方法就擺在第一位了,今天終于排上用場了。類方法、實例方法 都可以調用的,滿足各種需求。
比如在A頁面調整到B頁面,第一步我們想辦法已經創建了這個B對象了,上面的方法的作用就是讓B去調用他自己實現的方法,同時給傳遞一個參數,原理跟delegate一樣
下面是A頁面的代碼,我們假定有一個按鈕,點擊后執行如下的方法跳轉到B頁面并且傳遞一個參數
//傳遞參數
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];
那么我們在B頁面實現一下這個方法
-(void)setCouponFilter:(NSDictionary *)couponFilter{
NSLog(@"上個頁面帶來的參數:%@", couponFilter);
}
那么在B頁面就會看到輸出這個參數了,參數你拿到了,隨便怎么處理了
最后我們解決回調問題
block的方式詳見代碼,也類似于傳遞一個參數,參數為block。
delegate方式如下
可以定義一個協議,同時也要設置一個delegate,我們假定要A頁面稱為B頁面的delegate,那么以前我們會在B頁面的接口文件.h中聲明一個代理,現在聲明也沒有用了,因為看不到!那么如何設置呢?delegate也可以當做一個普通的參數傳遞,比如在A頁面將self,作為參數傳遞過去
SEL delegateSEL = NSSelectorFromString(@"setDelegate:");
if ([BVC respondsToSelector:delegateSEL]) {
[BVC performSelector:delegateSEL withObject:self];
}
在B頁面,我們就聲明一個delegate屬性,把set方法當做我們的傳遞參數方法
-(void)setDelegate:(id<ModuleDelegate>)delegate{
_delegate = delegate;
NSLog(@"delegate::%@", delegate);
}
在跳轉到B頁面的時候,會看到有delegate輸出了,這樣delegate也有了,我們就可以像普通的delegate方式去使用了
- 定義一個協議,大家都能看到的協議
- 為B頁面設置一個delegate,目前就A頁面
- 在A頁面中實現協議的方法,其實就是B讓A調用的方法
公共協議部分
@protocol ModuleDelegate <NSObject>
@optional
-(void)module:(id)obj info:(id)info;
B頁面某個操作后調用了這個方法
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);
}
這樣通信方式的基本原理就完事了。
這里的類名、方法名都屬于硬編碼,參數用的字典,為了去model化,就必須要求去維護一個Protocol,這里我定義了一個公共的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];
//設置delegate
SEL delegateSEL = NSSelectorFromString(@"setDelegate:");
if ([couponVC respondsToSelector:delegateSEL]) {
SuppressPerformSelectorLeakWarning(
[couponVC performSelector:delegateSEL withObject:self];
);
}
// 設置block
void (^block)(id) = ^(id couponObj){
NSLog(@"通過block回調:%@", couponObj);
};
SEL blockSEL = NSSelectorFromString(@"setBlock:");
if ([couponVC respondsToSelector:blockSEL]) {
SuppressPerformSelectorLeakWarning(
[couponVC performSelector:blockSEL withObject:block];
);
}
//傳遞參數
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];
*/
/** 解決編譯的時候出錯的問題 */
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
/**
通信協議,所有頁面的接口都在這個公共的協議方法里定義
*/
@protocol ModuleDelegate <NSObject>
@optional
/**
對外提供接口的模塊必須實現delegate設置的方法,當然方法叫什么名字無所謂 內部如何寫無所謂(你問我為啥叫setDelegate,就寫方法的時候少敲幾個字母,復用屬性的set方法了),主要是要拿到delegate
@param delegate 稱為其delegate的對象
*/
-(void)setDelegate:(id<ModuleDelegate>)delegate;
/*******************優惠券頁面通信協議***********************/
/**
設置接口參數
給優惠券頁面傳遞參數,方法名字無所謂(你問我為啥叫setCouponFilter,就寫方法的時候少敲幾個字母,復用屬性的set方法了),重點在obj這個參數上了,模塊內部如何處理這里不去管,這里直接實現了屬性的set方法
接口里的屬性建議放到.h接口文件中,便于模塊內部自己維護
@param obj 提供給模塊的參數
*/
-(void)setCouponFilter:(id)obj;
/**
優惠券頁面回調
@param obj 預留,傳遞什么無所謂,目前傳遞了本身,便于外部查看
@param info 回調的參數
*/
-(void)module:(id)obj info:(id)info;
/*******************優惠券頁面通信協議***********************/
@end
#endif /* ModuleProtocol_h */
還沒完事呢,大家會發現一個問題,就是如果執行下面的方法會有警告啊
performSelector: withObject:
可以這樣解決
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[someController performSelector: NSSelectorFromString(@"someMethod")]
#pragma clang diagnostic pop
為了解決這個問題我們定義一個宏
/** 解決編譯的時候出錯的問題 */
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
在調用的時候
//傳遞參數
SEL selector = NSSelectorFromString(@"setCouponFilter:");
if ([BVC respondsToSelector:selector]) {
SuppressPerformSelectorLeakWarning(
[BVC performSelector:selector withObject:@{@"name": @"This is a super order"}];
);
}
其他處理
這里我們集中在控制器處理上了,現實中夸組件調用各種各樣了,比如我要從 UserModel 獲取用戶信息,在UserModel 實現中有如下方法
-(NSDictionary*)getUserInfo{
NSDictionary* info = @{@"userId": @"8888", @"name": @"張三", @"age": @18};
return info;
}
那么我們在外部調用就可以獲取到信息
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);
}
如果需要調用工具類Utils里的方法處理數據,比如工具類Utils有個方法是給字典里的字符串追加前綴
+(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;
}
那么實際調用的時候
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);
}
總結了下我的方法有點類似于 Target-Action + 協議 的方式,當然這個只是簡單的實現,如果復雜的功能,那么還有去封裝一下,比如原生、H5切換,我的思路不一定是最好的,如果想看看大神們的通信方案,開篇提供了鏈接,大家可以去看看。
感謝您閱讀完畢,如有疑問,歡迎添加QQ:714387953(蝸牛上高速)。
github:https://github.com/yhl714387953/ModuleCommuication
如果有錯誤,歡迎指正,一起切磋,共同進步
如果喜歡可以Follow、Star、Fork,都是給我最大的鼓勵。