前言
什么是组件化,为什么要有组件化,组件化跟我们的项目又有什么关系,或者说它能给我们的项目带来什么,我们带着疑问来去了解组件化。
1 初识组件化
当我们项目比较庞大时,模块也会变得非常之多,项目将会变得难以维护,这个时候就会遇到以下几个问题:
- 模块间解耦
- 模块重⽤
- 提⾼团队协作开发效率
- 单元测试
怎么才能更好的解决以上几个问题,使App更加稳定,这个时候就需要我们用到组件化了。
我们把App划分成多个独立模块,每个模块提供相应的接口和相关功能以及Service提供其它模块使用,提高开发效率,模块之间可以解耦,各个独立模块可以重用,以及方便单元测试,这就是组件化思维。
什么样的项目需要组件化,是不是组件化可以解决一切问题?
答案:肯定是否定的。
- 项⽬较⼩,模块间交互简单,耦合少
- 模块没有被多个外部模块引⽤,只是⼀个单独的⼩模块
- 模块不需要重⽤,代码也很少被修改
- 团队规模很⼩
针对以几点就没有必要组件化了。
如何颗粒度的划分模块,划分的太细,也会增加模块之间的通讯成本,划分的太粗,模块之间的依赖就会变得复杂。
- 业务模块
- 首页,发现,关注等
- 通用模块
- 常用控件,数据管理,分享、三方登陆等
- 基础模块
- 底层组件,底层组件,比如网络请求库,宏定义,分类等文件
模块之间划分原则
只能上层对下层依赖,项目公共代码资源下沉,横向之间不能依赖,可以通过对下层的依赖解决
独立的模块可以搭建公司私有Cocoapods库,来进行模块的版本管理和依赖。
2 组件化模块创建
1.执行以下命令
pod lib create MacroAndCategoryModule
创建我们的这个模块模板,然后创建我们的文件,如图
需我们的Example工程中,pod install命令,就在测试工程中模块中的文件了。
-
接下来我们再创建另一个RoHomeModule模块,如图
3.我们再创建RoCommonUIModule这个模块,如图
3 组件化三方和本地组件依赖
RoCommonUIModule和RoHomeModule是依赖MacroAndCategoryModule
RoCommonUIModule同时依赖三方库,接下来我就解决这些问题。
RoCommonUIModule依赖了Masonry和AFNetworking,在这里怎么引入进去呢。
我们创建私有库的时候,生成了一个.podspec文件,如下
Pod::Spec.new do |s|
s.name = 'RoCommonUIModule' #库的名字
s.version = '0.1.0' #版本号
s.summary = 'A short description of RoCommonUIModule.' #简短描述
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
#详细描述
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
#主页
s.homepage = 'https://github.com/usename/RoCommonUIModule'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
#许可证
s.license = { :type => 'MIT', :file => 'LICENSE' }
#作者
s.author = { 'usename' => 'usename@126.com' }
#源码地址
s.source = { :git => 'https://github.com/usename/RoCommonUIModule.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
#支持的iOS最低系统版本
s.ios.deployment_target = '9.0'
#源文件配置
s.source_files = 'RoCommonUIModule/Classes/**/*'
# s.resource_bundles = {
# 'RoCommonUIModule' => ['RoCommonUIModule/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end
在这里我有看到依赖三方的例子
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
我们可以依葫芦画瓢,改成如下
s.dependency 'AFNetworking'
s.dependency 'Masonry'
然后pod install命令。
RoCommonUIModule同时又依赖了我自己创建的MacroAndCategoryModule库,如何引入进来呢。
s.dependency 'MacroAndCategoryModule'
s.prefix_header_contents = '#import "Masonry.h"', '#import "RoMacros.h"', '#import "UIKit+AFNetworking.h"'
然后Podfile文件改成这样的
platform :ios, '9.0'
target 'RoCommonUIModule_Example' do
pod 'RoCommonUIModule', :path => '../'
pod 'MacroAndCategoryModule',:path => '../../MacroAndCategoryModule'
target 'RoCommonUIModule_Tests' do
inherit! :search_paths
end
end
再执行pod install后,编译成功。
同样的RoHomeModule也是这么操作,这里就不再叙述了。
4 组件化资源文件加载
组件化的资源文件如何规划?
我们创建私有库的时候,RoHomeModule有两个文件夹,如
我们的文件是放在Classes中的,那么资源文件是否可以放在Assets,我们试下。
我们要用bundle的方式加载
NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:@"/RoHomeModule.bundle"];
NSBundle *resoure_bundle = [NSBundle bundleWithPath:bundlePath];
self.imageView.image = [UIImage imageNamed:@"share_wechat" inBundle:resoure_bundle compatibleWithTraitCollection:nil];
同时.podspec这个文件中的
s.resource_bundles = {
'RoHomeModule' => ['RoHomeModule/Assets/*.png']
}
要打开。
JSON以及其它文件加载方式
NSString *path = [[NSBundle mainBundle]
pathForResource:@"Home_Channel_List" ofType:@"json"];
NSData *data = [NSData dataWithContentsOfFile:path];
xib加载方式
NSString *bundlePath = [NSBundle bundleForClass:[self class]].resourcePath;
[self.tableView registerNib:[UINib nibWithNibName:className bundle:[NSBundle bundleWithPath:bundlePath]] forCellReuseIdentifier:className];
5 组件化CTMediator解耦通讯
我们的模块划分好了,库与库之间的依赖也解决了,那么模块与模块之间怎么交互,或者说怎么通信以及怎么解耦,我们来看下。
目前比较流行的是几种方式来处理模块与模块之间的通信
- Mediator中间层,A模块中,使用B模块,如果导入B模块的类就会产生耦合,传这个类的名字,利用URL方式(http://xxx/模块名字/事件动作/参数)即路由方式
- CTMediator为代表的target-action方式,在iOS调用方法,就是发送消息,消息有的接受者和消息主体,通过performSelector调起,返回值可以通过invocation灵活配置当前的target和selector。
6 组件化BeeHive解耦
[BHContext shareInstance].application = application;
[BHContext shareInstance].launchOptions = launchOptions;
[BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可选,默认为BeeHive.bundle/BeeHive.plist
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";
[BeeHive shareInstance].enableException = YES;
[[BeeHive shareInstance] setContext:[BHContext shareInstance]];
[[BHTimeProfiler sharedTimeProfiler] recordEventTime:@"BeeHive::super start launch"];
保存我们相关的数据。
其基本思想:
- 各个模块间从直接调用对应模块,变成以Service的形式,避免了直接依赖
- App生命周期的分发,将耦合在AppDelegate中的逻辑拆分,每个模块以微应用的形式独立存在
总结
本篇文章我们介绍了组件化的相关知识,知识略有浅薄,希望大家批可以提出见解,相互学习。