使用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目录结构如下:
Class目录下放置module代码,Assets目录下放置module的资源文件。Example目下是为我们创建的测试工程,在测试工程中引入了我们创建的module,来进行测试。然后我们将module相关的代码放到Classes目录下,切换到Example目录下执行pod install,然后example工程会更新module代码,对module进行测试使用。
如果我们创建的module依赖于其他的三方的module或者自己的私有module,那么就应该在我们创建的module工程的podspec文件配置依赖。例如依赖
AFNetworking
,Masonry
,ZFTestModel
,配置依赖和头文件如下:
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进行模块间通信的流程图如下:
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文件进行注册。