前言
网上的组件化很多,但是我个人觉得讲的不透彻而且也不容易理解。所以我决心写一篇文章,主要的目的是为了做笔记和大家一起探讨下。我说的并不一定是对的,因为ios这个东西我觉得太深奥很多东西我们都不知道。并且我可能不会一次性写完,因为我的时间有限我可能是空了更新一下并且在写这个东西的时候我很多东西如果模糊的我还得查查资料怎么解决。望各位同仁理解。
关于怎样建立私有仓库
先说一下我对组件化的大致理解:我个人觉得是组件化无非是把我们想要的各个模块抽取处理让他独立,你可以理解为一个模块为封装的一个sdk。他的主要目的是为了解耦。当然这里边就存在一个问题就是怎样模块和模块之间的通讯。因为我们如果不用组件化的话,把所有的代码都柔在一起的话那么就不存在这个问题了。解决模块之间通讯的问题的办法是:用一个中间件。当然中间件又存在两种比较流行的方式,下面会说道
要想知道组件化怎么弄,那么首先我们就需要建立私有库,当然你也可以建立公有库,但是正常的公司来讲是一般不会开源的,所以我们一般还是建立私有库,当然如果私有库你都会建立了那么公有库自然也就会了,所以这里直说私有库的建立
私有库的开始建立
首先我们要先看一个图
image.png
我们创建代码的时候我们一般都是本地有一份代码,然后我们上传到远端的话 那么别人就可以看到我们的代码,但是如果别人想要直接类似于集成sdk那种 直接pod一下就可以把我们的代码给集成到项目工程中 那该怎么办?
1.需要我们在上传代码中包含podspec文件,当我们的podspec上传到远端别人直接下载下来然后pod install 就可以了,其实这些都是要一步一步的来,所以首先我们需要知道是一些些基础知识:
1.1代码创建和提交,首先我们本地创建一份代码 xcode 默认勾选git,然后我们 首先是git add .,然后 git commit -’修改内容‘,最后我们提交到远端,但是我们这时候没有远端仓库,那么我们就首先创建一个远端的仓库,怎样创建一个远端仓库自己百度一下了奥,然后重要的问题是我们怎样让我们的远端仓库和我们本地代码库关联,我目前知道方式有两种第一种是我们创建一个远端代码仓库然后我们直接clone到本地,执行命令如下
git clone 远端代码的地址
但是这种方法有一个弊端就是如果我们本地直接存在很多代码了,那么这种不太方便了就,当然我们可以直接把我们的本地代码放到一个文件下中,等clone下来一个新的仓库我们在把文件夹copy到我们的仓库中,但是总归是不太友好
一般我经常使用的是第二种办法,当我们创建好一个本地的仓库的时候,我们也添加到我们本地代码仓库中了,那么我们推向远端的时候 我们执行这个命令
git remote add origin 远端仓库的地址
执行完这个命令我们在执行这个命令的时候就可以看到如下
git remote
image.png
这个时候我们就可以看到,我们可以找到远端了,接下来我们直接push上去(默认是master分支)
git push origin master
接下来我们就开始进行打tag,个人觉得tag就是一个标识,我们可以到时候直接拉tag的指定代码,别人在写版本的时候如果指定了版本号那么就拉以前的代码,其实他的作用还不止如此。但是他归根结底的作用就是一个标签,比如我们要给我们本地的代码打一个tag,tag号为:0.0.1
那么执行命令如下
git tag -a '0.0.1' -m '新建tag 0.0.1'
git push --tags
推送到远端可以看到在这个地方看到我们打的tag
image.png
然后我们想要直接只推上去一个tag,而不是吧所有的tag都提交上去,我们可以执行这个命令:(要在你当前的分支上,0.0.1为tag号)
git push origin 0.0.1
删除一个tag:(本地删除)
git tag -d 0.0.1
然后远端删除,就是把本地刚才删除的直接提交上去
git push origin :0.0.1
有必要解释下cocoapods 相关的知识
先画一个图
这个图我要解释一下:
首先我们的公有库,比如afn、sdwebimage这些,他们的描述文件在远程的cocoapods描述文件都会存在一份,当我们安装cocoapods的时候,当我们执行pod setup的时候会把远端的cocoapods的描述文件copy到我们本地一份,这个东西他会生成一个本地的索引文件,生成的索引文件是为了我们执行pod search更加方便和高效的搜索。先看下远程的cocoapod 的sepc文件,他都是存储的描述文件(这个描述文件是一般装了我们代码的真正的地址、简介、作者、依赖库等等)https://github.com/CocoaPods/Specs
大致的样子是这样的:
image.png
随便点开一个可以看到描述文件如下:
image.png
我们在看看经过我们pod setup之后在本地生成的spec描述文件
可以在这个路径下找到:
前往》个人》cocoapods》repos》cocoapods》specs》是和远端一模一样的,他生成的搜索的索引文件在这里:
前往》个人》资源库》repos》Caches》cocoapods》search_index.json
我们在执行pod install的时候会内部去到本地快捷搜索索引里面搜索,进而快速的找到搜索到spec的描述文件,找到描述文件也就找到了代码的远程地址,然后将代码下载到我们的工程中进而进行集成。
发布一个框架的大致步骤是:
1.首先我们将我们写好的代码上传到远程的仓库
2.将我们的描述文件验证好上传到cocoapods的描述文件库中(当然这是公有库)
3.第三部别人下载好,直接进行集成安装
本地私有库创建和使用
首先我们创建一个文件以及建立一些文件,如下所示:
image.png
我们要做的事情就是在我们的工程中引用这个Person.swift
image.png
首先我们在TestPerson文件下 执行这个命令
git init
git add .
git commit -m'本地初始化'
因为是本地所以我们没有必要提交到远端,这些命令只是为了有个git
然后我们在这个TestPerson文件下在执行这个命令:
pod spec create TestPerson
我们创建出一个spec文件,这个就是描述文件我们用来描述我们库信息的,创建出来的spec我们做如下的修改
image.png
image.png
然后我们保存,保存完了之后我们开始进行在test测试工程中来创建pod来进行引用他
我们在test工程中 执行
pod init
然后将Podfile文件改成这样子的
platform :ios, '9.0'
use_frameworks!
target 'test' do
pod 'TestPerson' ,:path => '../TestPerson'
end
解释:为什么我们要这样./TestPerson,因为我们要找本地的路径,所以pod语法就是这样的:path =>,我们要做的是从我们当前的Podfile文件中找到podspec文件的路径,当我们../的时候相当于到了这个路径
image.png
podspec在我们TestPerson路径下 所以我们最终的路径是 '../TestPerson',只要我们找到了podspec路径那么就是对的了,记住是相对路径。然后我们在test文件下执行这个命令
pod install --verbose
最后可以看到我们的宿主工程变成这个样子了
image.png
可以看到最后的我们工程中出现了Person.swift这个类了,也就是说我们集成本地成功了,注意 本地导入pod 是在 Development Pods 这个目录下
远程私有库的搭建
1.我们首先需要要创建一个远端的私有索引库,就是仿照cocoapods那样,比如我创建的名字是这样的
image.png
注意下边那两个我是都没有选择的我的目的就是创建一个干净的远端私有索引库(记住带上.gitignore,因为我们要看到分支)
2.然后我们创建本地的私有索引库并且与远端的私有索引库进行关联,我们执行如下的命令
pod repo add DGFMSpecs https://github.com/liudiange/DGFMSpecs.git
其中后面的那个远端私有索引库的地址,DGFMSpecs这个是本地的私有库,在任何的文件路径下执行都行,执行完了可以执行这个命令
pod repo
image.png
可以看到一个是我们的cocoapods 本地索引库,一个是我们刚刚创建的本地索引库,我们也可以到这个文件下看看
image.png
可以看到同样是存在的
3.我们创建本地的代码,然后把我们的代码提交到远端
首先我们切换到这个文件下
image.png
然后我们执行这个命令
pod lib create DGFMBase
因为我们现在要做的是一个基础组件,我们做的是放一个分类,我们的组件化一般分类三大类,基础组件、业务组件和功能组件,我们现在做一个工具类 、分类我们要放到基础组件里面去,关于组件化怎样划分和注意点后面会说道。
我们给我们的组件起了一个名字DGFMBase
创建出来的样子是这样的
image.png
当然在执行这个命令的时候,你会填写如下的信息,按照我目前的填写就行或者你根据他的提示按照实际情况填写
image.png
创建完毕他会跟我们生成一个测试工程,首先我们要在这个地方替换成我们写好的库
image.png
然后我们来填写podspec文件,当然在修改podspec文件的时候我们要创建一个远程代码仓库,按照如下创建
image.png
我们现在来配置我们的podspec 按照如下修改:
image.png
创建好之后我们可以本地验证一下,我们修改的podspec是否可以
执行如下命令
pod lib lint --allow-warnings
可以看到是通过了验证
image.png
注意要在这个文件下DGFMBase
如果这时候我们进行远程验证,执行如下的命令
pod spec lint --allow-warnings
应该是不行的因为我们没有把代码提交到远程仓库 还有我们没有打tag
所以我们首先将我们的代码提交到远程仓库,只想如下的命令(切换到DGFMBase路径下)
git add .
git commit -m'初始化'
git remote add origin https://github.com/liudiange/DGFMBase.git
git push origin master
这里注意一下 不要像我上面那样在默认的时候就添加gitignore,因为我们在只想找个命令的时候
pod lib create 库名
他已经生成了gitignore文件,所以就创建一个干干净净的远程代码地址就行了
image.png
上传到远端我们开始打tag,打tag的命令如下
git tag 0.1.1
git push --tags
注意点tag号要和我们的podspec中描述的一样
打好了之后我们可以在在这个地方看到
image.png
这个时候我们在开启我们远程验证一下,只想如下的命令
pod spec lint --allow-warnings
image.png
可以看到我们验证成功了,那么我们还剩下最后一个事情没有做就是我们要把我们刚才的做的podspec放到我们远程的私有索引库中去,只想如下命令
pod repo push DGFMSpecs DGFMBase.podspec --allow-warnings
注意首先要在DGFMBase文件下,其次我们需要在往这个库中DGFMSpecs push这个文件DGFMBase.podspec,也就是这个意思
image.png
当我们往本地私有索引库进行push的时候他会自动验证和push到远程私有索引库,因为我们前面用一个命令把本地的私有索引库和远程私有索引库给关联到一起了
可以看到我包了一个错误
image.png
原因是这样的我开始创建远程私有索引库的时候创建的是一个干净的仓库 没有任何东西也就是没有master,可以执行
pod repo
可以看到
image.png
所以删除我们的远程私有仓库从新创建并且勾选上。gitignore
最后经过我们一番处理最终成功了
image.png
也可以看到在这个地方
image.png
到此我们开始创建一个测试工程来开始验证我们的结果吧
创建了工程并且测试工程中的Podfile 如下修改:
platform :ios, '9.0'
use_frameworks!
source 'https://github.com/liudiange/DGFMSpecs.git'
source 'https://github.com/CocoaPods/Specs.git'
target 'test' do
pod 'DGFMBase'
end
然后我们执行pod install 可以发现执行成功了,为什么要这样做
source 'https://github.com/liudiange/DGFMSpecs.git'
source 'https://github.com/CocoaPods/Specs.git'
就是为了在私有库中和外面的公有库中都可以搜到东西
最终的结果可以如下:
image.png
仔细观察可以看到是在pods里面因为这是远程的不是本地的
远程私有库的优化(也可以理解为本地私有库的优化)
1.优化远程私有库添加依赖库的那种
比如我们自己创建的库中 要依赖一个三方库 比如AFNnetwork
那么我们需要怎么做?我们应该这么做
在这个势例工程中添加中的podspec文件中我们添加
image.png
我添加了两个依赖库
然后将代码修改提交到我们的远程仓库区
git add .
git commit -m'添加依赖库'
git push origin master
然后我们验证一下 看看我们的podspec是否ok
pod spec lint --allow-warnings
验证通过了 发现我们配置的依赖库是没啥问题的
然后将我们的podspec 提交到我们自己私有索引库中去
pod repo push DGFMSpecs DGFMBase.podspec --allow-warnings
可以发现也是成功的,然后我们就在我们的测试工程中执行
pod install --verbose
看看是否能够自动导入我们需要的库,可以看到
image.png
成功的导入了我们需要的库,其实我们这样做有点小问题就是其实像这样的改动我们就需要添加tag的为了防止以前别人用了我们的代码人家没有写版本,这种改动会有影响所有我们一般来说还是要加tag的
2.我们来创建一个子subspec
为什么这么说呢,因为我们一个基础组件我么可能是有工具类、分类 等的,但是工具类中可能依赖某些库,但是如果我们只是想用分类的话 那么我们没有必要导入工具类 也没有必要添加依赖库,所以我们这么处理,看下如下操作,比如我当前的自己的库是这样的
image.png
image.png
其中我要在tool这个工具类中添加依赖,依赖afn,那么我们的podspec文件就要这样写
image.png
可以看到我们注释掉了这个 是s.source_files,因为他是整个DGFMBase库的,我们写s.subspec 其中‘Category’对应于这个地方
image.png
do 后边的 c其实起的是一个别名 用来代表Category ,然后我们注意的一点是要在这个地方写好我们的路径
DGFMBase/Classes/Category/**/*
然后我们注释全局的库依赖,我们改为在tool子库中添加依赖这样的话,我们在只有导入tool这个库的时候我们才会导入afn,因为放到外面是所有的都导入
然后我们也是按照上面的步骤 提交代码、远程验证、提交远程私有索引库,当我在执行pod spec lint 验证的时候发现除了一个这个问题
image.png
很奇怪我的路径是没有问题的,这是怎么回事呢?后来我发现如果我要添加依赖库这些小动作的时候是可以不用重新打tag的,但是比如大一点的改动 比如像我这种弄一个subspec的这种的话 是要重新打tag的,我打了一个tag并且提交到远端,然后在远程验证发现好了。
然后我们需要在test工程中的podfile文件中这样写
platform :ios, '9.0'
use_frameworks!
source 'https://github.com/liudiange/DGFMSpecs.git'
source 'https://github.com/CocoaPods/Specs.git'
target 'test' do
pod 'DGFMBase/Category'
end
然后pod install 发现我们的工程变成这样了
image.png
说明达到了我们的目的,然后我们在这样写
platform :ios, '9.0'
use_frameworks!
source 'https://github.com/liudiange/DGFMSpecs.git'
source 'https://github.com/CocoaPods/Specs.git'
target 'test' do
pod 'DGFMBase/Category'
pod 'DGFMBase/Tool'
end
发现我们的工程直接引入了afn
image.png
其实如果导入两个字库的话 我们还可以这样写
platform :ios, '9.0'
use_frameworks!
source 'https://github.com/liudiange/DGFMSpecs.git'
source 'https://github.com/CocoaPods/Specs.git'
target 'test' do
pod 'DGFMBase',:subspecs => ['Category','Tool']
end
这样的话 我们得到的结果和 上边的是一样的
image.png
然后如果我们想要导入整个工程我们也可以这样写
platform :ios, '9.0'
use_frameworks!
source 'https://github.com/liudiange/DGFMSpecs.git'
source 'https://github.com/CocoaPods/Specs.git'
target 'test' do
pod 'DGFMBase'
end
3.解决在工程中引用图片文件的问题,我们按照上边的步骤我们可以在创建一个业务组件,比如叫 home组件,然后我们在那里边创建一个控制器 然后,我们引用图片看看怎么样的
比如我创建好了一个home组件,然后我们在home组件红podspec中要这样配置
image.png
一个关键点就是打开这个注释,然后我们如果放了jpg或者gif之类的我们可以这样写
s.resource_bundles = {
'DGFMHomeComponent' => ['DGFMHomeComponent/Assets/*']
}
也就是允许所有的,然后还要注意的是要这些,假如xib我们要这样写导入图片,首先我们要看先我们外部是怎么使用我们testviewcontroller的,是这样使用的
let bundle = Bundle.init(for: TestViewController.self)
let vc = TestViewController.init(nibName: "TestViewController", bundle: bundle)
self.view.addSubview(vc.view)
image.png
为什么不直接写创建的方法,还要找到他的bundle因为,我们的testviewcontroller的xib 是在这个文件下的,我们可以在这个地方找到他的位置
image.png
我们可以通过show in finder 下的这个
image.png
然后显示包内容,最后在DGFMHomeComponent.framework下找到,因为我们如果直接创建的话 那么默认的是mainbundle,mainbundle对应的路径是这个
image.png
所以找不到,也就是不会调用xib内部的方法,向我上边那样调用了,但是如果在xib中直接饮用图片了运行程序发现还是看不见图片,我先给大家看下我的testviewcontroller xib 是怎么做的
image.png
运行项目还是没有看到图片,倒是看到绿色的背景了,说明我们图片的路径不对,首先我们看下我们的图片在哪
我们点击这个地方
image.png
然后show in finder 然后在
image.png
显示包内内容,最后我们在这个路径下找到了图片
image.png
然后我们在显示包内容发现了图片,
image.png
所以我们应该这样引用图片
image.png
然后我们运行发现出来了图片,我们这是通过xib方式创建的,那么假如我们通过代码方式改怎么写呢,我们可以这样写
let imageView = UIImageView()
imageView.frame = self.view.bounds
imageView.backgroundColor = UIColor.red
let currentBundle = Bundle(for: TestViewController.self)
guard let path = currentBundle.path(forResource: "WechatIMG6224.png", ofType: nil, inDirectory: "DGFMHomeComponent.bundle") else {
return
}
let image = UIImage(contentsOfFile: path)
imageView.image = image
self.view.addSubview(imageView)
我们值得注意的是 WechatIMG6224.png 这个要写全名,不能少,然后还有就是其实类似这种 我们可以封装一个工具类 然后外部传递参数来显示就可以了,我们就不封装了,自己可以封装一下,其实组件化这就是其中的一个小注意点,遇到类似的问题我们就像我上边那样分析就行了,因为不止图片的问题 还有其他的问题等等
组件化的设计原则
组件化一般这样划分:功能组件、业务组件、基础组件
1.业务组件:
业务组件顾名思义就是按照业务来划分的,我们假如拿微信来说,那么我们的首页是一个组件、发现模块是一个组件、设置模块是一个组件等等
2.功能组件:
其实就是单一某一个功能来划分的,比如我们做直播项目 ,那么我们的弹幕我们可以单独给他封装成一个单独的组件,这个组件是单独存在的,不依赖任何东西的,到时候我们单独拿出来直接到业务组件中去使用就可以了,或者到别的组件中去使用。
3.基础组件:
他主要是做我们一些基础的事情,比如我们可以把网络封装放到这里面,或者我们一些分类、工具类等等都放到这里面。
注意事项:
1.一般来讲 功能组件和基础组件是单独存在的,不会相互之间产生依赖,比如我们的功能组件中需要网络请求的可以使用基础组件中的网络请求,但是原则上是不友好的,因为这样就产生了组件之间的依赖。如果出现类似的现象我们可以通过使用协议或者代理的方式往外抛,让具体使用的组件去实现。
2.一般来讲我们做底层组件的时候,比如我们封装网络层 ,我们可能会用到afn等等之类的,那么我们需要在内部对她进行包装 防止那天我们换框架的时候方便替换,这样的好处就是上层没有必要动,下层修改即可。
3.组件之间的关系是,业务组件依赖基础组件,业务组件依赖功能组件。
业务组件之间怎样通讯(重点)
首先我们在做组件话的时候我们需要业务组件和业务组件之间通讯,拿微信来说我们可能会遇到这样的需求就是从首页跳转到发现页面,那么他是两个组件那么我们直接引用是不可以的,出现我们在内部各个属性都给他设置为public或者open,如果设置为public 或者open 那么我们也就不是组件化了,因为我们用组件化的目的就是为了解耦,那么的话 也就解不了耦。所以我们需要组件之间的通讯。
1.通讯方式一:MGJRouter(路由的方式)
首先我大致画一个图。
可以看到组件之间的联系总是与中间件进行联系。然后我们把中间件作为桥梁来进行构建。
1.1 具体怎样实现:
https://www.jianshu.com/p/5e997617c27c
其实我也是看别人的文章学习的,我觉得这篇文章还挺好的。然后如果你还是不怎么知道 可以搜索 蘑菇街 - router 这样的话就会出现你想要的结果。我们目前的项目就是采用这样路由方式。因为有一个问题就是 大量的注册会带来启动耗时,虽然一个方法耗时不长可能几毫秒,那么越来越多就会产生不小的耗时。而且这个也不是真正的解耦,因为我们总是和中间件存在点关系或者依赖。然后这是我的demo
https://github.com/liudiange/MGJRouterDemo
- 通讯方式2:target - action (运行时的方式)
他这个方式是我很赞同的一个方式,他是这样做的,他写了一个工具叫CTMediator,这个工具其实他的作用就是运行时查找的一个东西,他现在已经完美解决了他的弊端,之前是只是这样的一个CTMediator,但是这样的话有一个弊端就是别人不知道改往你的调用函数中传递什么,后来这个作者提出通过添加分类的方式,也就是做组件那个人 他在外部的时候给人家提供出参数传递方法,然后别人一看名字就知道怎么回事了。其中我写了一个demo(当然我没有支持远程的,我支持的都是本地,其实在这里远程和本地道理上都是一样的)
代码地址在这里
https://github.com/liudiange/MediatorDemo
然后大家最好看下原文,因为我这样给你说 如果你开始没有做过组件化的 可能还是不明白,所以我把作者原文地址给你
https://casatwy.com/modulization_in_action.html
分析确实是target - action (运行时的方式)这种方式更是彻底的解耦,如果在实际项目中我还是比较建议使用这种方式的。
总结:
我这篇文章只是总了一些市场上比较认可的方式,其中我觉得重点是组件化是怎么划分、中间件最重要的我觉得是pod 我上边那些知识,其实只要掌握那些知识,别人写的文章也就可以看得懂了。(关于以上pod 那些demo 在我的git主页上 https://github.com/liudiange/)
网友评论