组件化

使用cocoapods创建自己的组件

命令行执行pod lib creat 组件名命令,创建自己的组件。然后就会让你输入一系列的配置:

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 

配置完成后,会自动创建module以及测试的工程。创建的module目录结构如下:

module目录结构

Class目录下放置module代码,Assets目录下放置module的资源文件。Example目下是为我们创建的测试工程,在测试工程中引入了我们创建的module,来进行测试。然后我们将module相关的代码放到Classes目录下,切换到Example目录下执行pod install,然后example工程会更新module代码,对module进行测试使用。
如果我们创建的module依赖于其他的三方的module或者自己的私有module,那么就应该在我们创建的module工程的podspec文件配置依赖。例如依赖AFNetworkingMasonryZFTestModel,配置依赖和头文件如下:

s.dependency 'AFNetworking'
s.dependency 'Masonry'
s.dependency 'ZFTestModel'
s.prefix_header_contents = '#import "Masonry.h"','#import "UIKit+AFNetworking.h"','#import "LGTest.h"'

这样配置完成后还没有结束,因为ZFTestModel是我们私有的module,cocoapods是无法直接通过spec找到的,需要手动配置我们的私有module查找路径,在example工程的Podfile文件中指定:

pod 'ZFTestModule', :path => '../../ZFTestModule'

这样配置完成后就可以在example工程中测试我们的module了,执行pod install,然后就可以正常使用了。

注意点:加载图片或者json的时候,首先我们需要把图片或者json文件放到我们上面提到的Assets文件中。然后在podspec中配置资源的查找路径:

s.resource_bundles = {
     'LGModuleTest' => ['LGModuleTest/Assets/*']
   }

配置然后在加载资源(图片、plist文件、xib等)的时候需要制定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,执行example工程,就可以加载资源了。

CTMediator方式进行模块间通信

使用CTMediator可以在模块间通过scheme的形式进行通信,这样就可以实现解耦合。

scheme://[target]/[action]?[params]
url sample:
aaa://targetA/actionB?id=1234

1、处理scheme,将scheme中的target、action和params分离出来;
2、使用runtime生成对应的target对象,action生成SEL;
3、如果返回值为基本数据类型和void使用NSInvocation实现对Target的SEL的调用,否则使用performSelector执行对应的SEL。

CTMediator属于底层的基础层,但是我们A和B模块通信,比如A模块要跳转到B模块的DetailViewController,需要引入DetailViewController,然后创建该视图控制器进行跳转。或者A模块需要对B模块的一个视图进行赋值,也需要拿到B模块的视图,然后取出来参数进行赋值。这些代码不能包含在CTMediator层,因为它是基础模块,不能包含业务代码,不能对上层的业务代码产生依赖。那么此时就会有一个中间层Target-Action层。Target-Action是跟随着业务模块B进行维护的,相当于模块B对外暴露的功能接口通过自己的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
{
    // 因为action是从属于ModuleA的,所以action直接可以使用ModuleA里的所有声明
    DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
    viewController.valueLabel.text = params[@"key"];
    return viewController;
}

那么我们怎么使用CTMediator进行模块间通信呢?直接使用CTMediator,然后拼接scheme来进行通信当然也可以。但是感觉没有那么友好,使用过程中也容易出错。那么我们可以对CTMediator进行一次封装,通过给CTMediator增加Category的方式,来实现封装好每个Target-Action提供的功能。这样我们调用的时候,直接调用我们自己封装的这一层Category的方法,传递进来相关的参数,就可以实现通信了。这个Category可以封装成独立的一个module,跟随着业务进行维护。
例如下面为#import "CTMediator+CTMediatorModuleAActions.h"的内容:

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 {
        // 这里处理异常场景,具体如何处理取决于产品
        return [[UIViewController alloc] init];
    }
}
@end

使用CTMediator进行模块间通信的流程图如下:

使用CTMediator进行模块间通信

BeeHive方式进行模块间通信

一到多的通信:

系统事件的分发:比如Application的相关事件,包括启动、进入前台、进入后台等事件。按照我们一般的方式,如果我们需要在app进入前台的时候做一些事情,就得在AppDelegate中写一些代码。但是项目组件化后,如果在AppDelegate的代理方法中写入各个module的代码,耦合性也是比较强的。那么我们可以将这部分app生命周期的相关方法进行下沉,然后其他的模块就可以直接获取到app生命周期的回调了。整个过程如下:

  • 1、AppDelegate集成与BHAppDelegate,那么App的生命周期都会被下层的BHAppDelegate做拦截;
  • 2、AppDelegate各个需要提供给其他模块的方法封装成protocol的形式来提供给外面;
  • 3、在需要获取到App生命周期的模块中,通过BHModuleManager进行注册成为观察者,同时遵守protocol协议,实现协议方法。BHModuleManager会绑定一个Context,context中保存了App的一些全局的信息;
  • 4、BHAppDelegate在对应的事件发生的时候通知遵守协议的注册者。

这样的话,其他的module,想要在app的生命周期方法中做些事情,只需要通过BHModuleManager注册就可以了。而不需要直接和AppDelegate进行耦合。

点到点的通信:

上面介绍了BeeHive一对多的通信方式,下面我们介绍下一对一的通信方式:
一对一的通信方式是通过协议和类对象绑定的方式。通过注册的方式将Class和protocol绑定在一起,也就是将Class和protocol放到Dictionay中以键值对的方式存起来,然后想要获取到Class,只需要拿到protocol,然后到Dictionay中就能拿到对应Class对象。Class对象可以执行protocol中的方法。
注册的方式分为两种:一种是动态的注册;一种为静态注册。动态注册是在Class文件中使用宏声明该类为模块入口。静态注册是将Class和protocol写入plist文件中,然后应用启动的时候回去加载plist文件进行注册。

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

推荐阅读更多精彩内容