組件化開發之組件通信原理(一)

組件化架構、通信方案想必大家都看到很多,蘑菇街李忠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,都是給我最大的鼓勵。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,837評論 18 139
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,199評論 30 471
  • 前言: 本文轉自前同事casa的博文,這篇文章是基于runtime實現的iOS組件化方案,其實iOS組件化方案基本...
    monkey01閱讀 1,675評論 1 2
  • iOS網絡架構討論梳理整理中。。。 其實如果沒有APIManager這一層是沒法使用delegate的,畢竟多個單...
    yhtang閱讀 5,236評論 1 23
  • 這幾天我一直在想 怎么去理解演講 怎么去練好演講 今天我終于在洗衣服的時候得到了一點靈感 演講好像做菜 說烹飪可能...
    如果我是一朵發閱讀 180評論 0 1