组件化开发之组件通信原理(一)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 前言: 本文转自前同事casa的博文,这篇文章是基于runtime实现的iOS组件化方案,其实iOS组件化方案基本...
    monkey01阅读 1,630评论 1 2
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,101评论 1 23
  • 这几天我一直在想 怎么去理解演讲 怎么去练好演讲 今天我终于在洗衣服的时候得到了一点灵感 演讲好像做菜 说烹饪可能...
    如果我是一朵发阅读 160评论 0 1