最近想了解一些组件化的知识,去看了 Casa 写的 iOS 应用架构谈 组件化方案这篇文章,Casa 在文中针对蘑菇街的组件化方案提出了一些不同的观点,陈述了自己的组件化方案。
大神们讨论具体的实施方案,是对理论的描述,在架构层面来分析利弊,我看过之后感觉还是有点晦涩,具体的方案异同之处我们先不说,今天我们先从 应用 着手,在自己当前的工程实施组件化。
当然了,我们选择使用的方案是 Casa 的CTMediator。
准备
首先我们得先了解组件化这个概念,其实通俗的讲,就是把我们的项目拆解成一个一个的小组件分别管理。我们平时使用 cocoapods 继承的三方的库,可以理解成是一个公有的组件。我们项目中,也可以把一些模块拆解出来,使用 cocoapods 来集成。这样拆解成一个个的组件的好处有很多,比如说业务模块之间解耦,复用模块,节省编译时间等等。
所以我们要先学会创建 cocoapods 私有库。
这里多说一句,Casa 的组件化方案在实施的时候,每独立出来一个组件,就会相应的创建一个 Category 工程,作为中间的调度,所以说,我们每做一个组件,就要创建两个私有的 pod 工程。
我们结合 Casa 这篇在现有工程中实施基于 CTMediator 的组件化方案,来做一下补充或者说是注解吧,本文中的流程取自于上文。
创建私有 Pod 工程
1. 先去开一个 repo,这个 repo 就是我们私有 Pod 源仓库
2. pod repo add [私有 Pod 源仓库名字] [私有 Pod 源的 repo 地址]
3. 创立一个文件夹,例如 Project。把我们的主工程文件夹放到 Project 下:~/Project/MainProject
4. 在~/Project 下 clone 快速配置私有源的脚本 repo:git clone git@github.com:casatwy/ConfigPrivatePod.git
5. 将 ConfigPrivatePod 的 template 文件夹下 Podfile 中 source 'https://github.com/ModulizationDemo/PrivatePods.git' 改成第一步里面你自己的私有 Pod 源仓库的 repo 地址
6. 将 ConfigPrivatePod 的 template 文件夹下 upload.sh 中 PrivatePods 改成第二步里面你自己的私有 Pod 源仓库的名字
首先我们先创建一个名为 Project 的文件,然后把我们项目的主程序,我们叫做 MainProject 放到 Project 路径下,然后在 Project 路径下 clone 出我们需要的脚本(Casa 提供)
在~/Project 下 clone 快速配置私有源的脚本:git clone git@github.com:casatwy/ConfigPrivatePod.git
现在我们的文件目录结构是这样的。
Project
├── ConfigPrivatePod(脚本文件)└── MainProject
在 Project 路径下创建我们的组件工程(一个普通的 iOS 工程),我们把这个工程名字叫 PayComponents(模拟抽取项目中的支付模块)。
当前目录结构
Project
├── ConfigPrivatePod
├── MainProject
└── PayComponents
有了本地的工程之后,我们现在需要创建一个 repo,作为我们的私有 pod 源仓库。也就是在 github,或者 gitee(码云)上面创建一个项目,放我们的项目代码,命名 PayComponents。
然后呢,我们还需要创建一个东西,就是私有 Pod 源仓库名字。
pod repo add [私有 Pod 源仓库名字] [私有 Pod 源的 repo 地址]
落实到我们这个项目中,我们应该这样写。
pod repo add PayComponents https://gitee.com/LittleBin/PayComponents.git
那这到底代表着我们创建了什么?
我们打开 finder-> 前往 -> 前往文件夹,然后输入~/.cocoapods/repos
可以看到目录是这样子的
repos 路径下面有一个 master,一个 payComponents。这两个文件夹我们可以粗略的认为他和 pod search 还有 install 有关。
打个比方就拿 search 来说,我们查询一个库的时候会用下面这个命令
pod search AFNetworking
然后会从 master 路径下找到 AFNetworking,然后列出来他有哪些版本什么的。我们有的时候会发现一个库其实已经跟新到 2.x.x 版本,但是我们 search 出来只有 1.x.x,这也可能是我们的 cocoapods 没有更新,我们的 master 路径下没有新的版本。
这个 PayComponents 文件夹,就代表我们本地有一个私有的 pod 库。我们 search 的时候,也会查这些本地的私有库。
下面把远程的这个 repo 和我们本地创建的项目关联到一起,这个工作 Casa 给我们提供的脚本就可以完成,顺便还会帮我们生成.podspec 的文件,具体这个文件的作用我们后面再说,还会初始化 podfile。
我们进到 ConfigPrivatePod 文件中,执行 config.sh 脚本,然后在终端根据提示输入就行了。
[localhost:ConfigPrivatePod sunxiaobin$ ./config.sh
Enter Project Name: PayComponents
Enter HTTPS Repo URL: https://gitee.com/LittleBin/PayComponents.git
Enter SSH Repo URL: git@gitee.com:LittleBin/PayComponents.git
Enter Home Page URL: https://gitee.com/LittleBin/PayComponents
================================================
Project Name : PayComponents
HTTPS Repo : https://gitee.com/LittleBin/PayComponents.git
SSH Repo : git@gitee.com:LittleBin/PayComponents.git
Home Page URL : https://gitee.com/LittleBin/PayComponents
================================================
confirm? (y/n):y
copy to ../PayComponents/FILE_LICENSE
copy to ../PayComponents/.gitignore
copy to ../PayComponents/PayComponents.podspec
copy to ../PayComponents/readme.md
copy to ../PayComponents/upload.sh
copy to ../PayComponents/Podfile
editing...
edit finished
cleaning...
Initialized empty Git repository in /Users/fmb/Documents/LEARN/Project_test/PayComponents/.git/
clean finished
finished
localhost:ConfigPrivatePod sunxiaobin$
Enter Project Name: 的时候,后面的名字一定要跟我们创建的 PayComponents 工程一样,要不然脚本找不到文件,就配置不了。
脚本跑完之后 PayComponents 里面变成下面这样子。
我们要修改一下 Podfile 文件和 upload.sh。下面是生成的 Podfile 文件里面的内容
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
source 'https://github.com/ModulizationDemo/PrivatePods.git'
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
target 'PayComponents' do
end
因为脚本都是照着模板来生成的这些文件,上面是 podfile 文件里面的内容,我们要把第一个 source 后面的 'https://github.com/ModulizationDemo/PrivatePods.git'
改成我们这个库的地址,https://gitee.com/LittleBin/PayComponents.git
。
然后在 upload.sh 中,最后一行
pod repo push PrivatePods PayComponents.podspec --verbose --allow-warnings --use-libraries --use-modular-headers
把 PrivatePods 替换为我们上面的私有 pod 库名称。
也就是我们之前执行的下面这个命令中的 PayComponents。
// 不用重复执行
pod repo add PayComponents https://gitee.com/LittleBin/PayComponents.git
我们在看一下 PayComponents 项目文件的结构,脚本给我们生成了一个空的 PayComponents 文件。
我们需要把这个文件拖拽到我们的项目中
然后我们这个 PayComponents 组件的文件就全部放到这个路径下。我们的私有库暂时就先写到这里。下面创建我们的 Category 工程
创建 Category 工程
因为篇幅原因,这里我把创建 Category 工程略过一下,不一一展示了。
其实按道理说在实际项目中,这个 Category 工程也要做成私有库的,就像我们上面说的步骤,先创建本地工程,创建远程的 repo,然后创建本地私有库(pod repo add …),在用脚本关联本地和远程的库,然后修改 Podfile 和 upload.sh 等等这一整套步骤。
最终 Category 项目的结构是下面这样的。(包含为 CTMediator 添加的分类)
我们调用 PayComponents 组件里面的 vc 都是通过这个 category 来调用的。所以说我们的这个 Pay_category 工程应该在 Podfile 文件中加上pod 'CTMediator'
,然后执行pod install
。
我们之前有遇到过这种情况,我们在使用 A 库的时候,pod install 之后,会自动帮我们导入 A 库依赖的 B 库,回到我们的项目也就是说当我们在主工程里面集成 Pay_Category 这一组件的时候,应该默认帮我们把 CTMediator 库和 PayComponents 组件也集成进工程。这就要修改 Pay_Category.podspec 文件。
在文件的最底部,end 之前加上下面两句
s.dependency "CTMediator"
s.dependency "PayComponents"
说回我们新建的这个 CTMediator+pay 文件,我们要在主工程 MainProject 跳转到 PayComponents 里面的 VC,所以 CTMediator+pay 应该提供一个返回 vc 的方法,如下:
- (UIViewController *)PayViewController {return [self performTarget:@"PayManager" action:@"pay" params:nil shouldCacheTarget:NO];
}
我们想不去管 return 后面的代码是什么意思,只要他是返回了 vc 给我们就可以。
然后在 MainProject 里面的跳转代码就如下:
// 导入 CTMediator+pay 头文件
UIViewController * viewController = [[CTMediator sharedInstance] PayViewController];
[self.navigationController pushViewController:viewController animated:YES];
下面我们再来说 return [self performTarget:@"PayManager" action:@"pay" params:nil shouldCacheTarget:NO];
是什么意思。
我们可以从 CTMediator 的源码中,看一下 - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
这个方法。
那这里我就不绕圈子里,直接说结论,其实在上述的方法中是找到了一个叫 Target_PayManager 的类,调用了它里面的 Action_pay 方法,这个前缀 Target_和 Action_是在方法内部拼接的,所以说我们可以得出,CTMediator 是通过反射拿到类名和方法名,然后调用,得到目标 vc 的。所以说在 PayComponents 工程中,我们还得创建 Taregt 文件来做间接的调用。
这里的 Target_PayManager 文件的文件名后半部分 PayManager 要跟 - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
里面的 targetName 对应起来,然后类里面的方法名字也得对应。(PayViewController 就是我们的目标文件)
- (UIViewController *)Action_pay:(NSDictionary *)param {PayViewController *viewController = [[PayViewController alloc] init];
viewController.param = param;
return viewController;
}
最后我们在 MainProject 的 Podfile 文件中引入 Pay_Category 库就行了。这个时候我们的私有库可能还没有完全创建成,所以我们可以用这种导入本地的方法
pod "Pay_Category", :path => "../Pay_Category"
当然如果私有库做好了,就只需要 pod "Pay_Category"
就行了。
完善私有库
在 Casa 的博客中说到,可以使用他的 upload.sh 脚本来更新私有库代码,我用过它的脚本,总是有错无,而且库的版本号也是依次 + 1 的形式,版本号我喜欢 x.x.x 这种,所以我选择自己提交代码,更新私有库。
针对于每个工程来说,首先是一些基本的提交代码操作,
git add .
git commit -m“新版本号“
git tag 新版本号
git push origin master —tags
要更新私有库,我们拿 PayComponents 这个项目来说,执行下面的指令
// 私有库升级
pod repo push PayComponents PayComponents.podspec
这个 PayComponents 就是我们 pod repo add 时候起得名字,后面是.podspec 文件,这操作等于把我们的私有库更新推到我们本地的库里面。注意.podspec 里面的版本号要记得更新,与 tag 一致。
那这种私有库我们项目的其他成员,还是拿 PayComponents 这个来说,首先在代码管理库上面得给他们下载代码的权限,然后执行下面:
// 待核实~
pod repo add PayComponents https://gitee.com/LittleBin/PayComponents.git
公有库
补充一点,那如果想做成公有库,让大家都可以使用要怎么搞?
执行下面操作:
pod trunk push PayComponents.podspec
就可以把组件推到 Cocoapods 主仓库。别人就可以通过 pod search 来查找你的库了。
pod trunk push 可能失败,因为首次使用 trunk 需要注册自己的电脑。
pod trunk register [E-mail] [User Name]
执行完成之后,会受到一封验证邮件,按邮件提示完成验证即可。
全部都完成了之后 pod search 也可能会搜不到自己的库,这时候可以尝试把缓存删掉
使用命令:rm ~/Library/Caches/CocoaPods/search_index.json
清除后,再重新搜索,此时 CocoaPod 会重新创建搜索索引。
这只是一些可能的原因,具体的问题需要具体针对解决。
参考文章
https://casatwy.com/modulizat…
https://www.jianshu.com/p/59c…
https://www.jianshu.com/p/757…
https://www.jianshu.com/p/1d8…