组件化的理解与运用

前言

网上的组件化很多,但是我个人觉得讲的不透彻而且也不容易理解。所以我决心写一篇文章,主要的目的是为了做笔记和大家一起探讨下。我说的并不一定是对的,因为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 相关的知识

先画一个图

image.png

这个图我要解释一下:
首先我们的公有库,比如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(路由的方式)
首先我大致画一个图。

image.png

可以看到组件之间的联系总是与中间件进行联系。然后我们把中间件作为桥梁来进行构建。
1.1 具体怎样实现:
https://www.jianshu.com/p/5e997617c27c
其实我也是看别人的文章学习的,我觉得这篇文章还挺好的。然后如果你还是不怎么知道 可以搜索 蘑菇街 - router 这样的话就会出现你想要的结果。我们目前的项目就是采用这样路由方式。因为有一个问题就是 大量的注册会带来启动耗时,虽然一个方法耗时不长可能几毫秒,那么越来越多就会产生不小的耗时。而且这个也不是真正的解耦,因为我们总是和中间件存在点关系或者依赖。然后这是我的demo
https://github.com/liudiange/MGJRouterDemo

  1. 通讯方式2:target - action (运行时的方式)
    他这个方式是我很赞同的一个方式,他是这样做的,他写了一个工具叫CTMediator,这个工具其实他的作用就是运行时查找的一个东西,他现在已经完美解决了他的弊端,之前是只是这样的一个CTMediator,但是这样的话有一个弊端就是别人不知道改往你的调用函数中传递什么,后来这个作者提出通过添加分类的方式,也就是做组件那个人 他在外部的时候给人家提供出参数传递方法,然后别人一看名字就知道怎么回事了。其中我写了一个demo(当然我没有支持远程的,我支持的都是本地,其实在这里远程和本地道理上都是一样的)
    代码地址在这里
    https://github.com/liudiange/MediatorDemo
    然后大家最好看下原文,因为我这样给你说 如果你开始没有做过组件化的 可能还是不明白,所以我把作者原文地址给你
    https://casatwy.com/modulization_in_action.html
    分析确实是target - action (运行时的方式)这种方式更是彻底的解耦,如果在实际项目中我还是比较建议使用这种方式的。

补充:

liop 相关的指令

1.从一个合成的framework中移除一个具体的一个某一个架构(比如移除i386)

liop -remove i386 xxxx.framework -output aaa.framework

2.从一个组合的framework中要获取具体的某一个架构(arm64)的可执行文件

liop -thin arm64 xxx.framework -output aaa.framework

3.查看一个可执行文件的具体都支持哪些架构

liop -info aaa.framework

4.将某些架构合在一起生成一个新的可执行文件(记住:不是外面的framework 是里面那个可执行文件的framework ,最好是全路径这样自己不会出错)

liop -create a.framework b.framework -output c.framework
自动打包的操作处理 (fastlane 和 jenkins)

1.fastlane的安装和使用
fastlane 我其实对他理解的不深入,因为我也是刚刚学习他的,他的主要作用是一个自动化的脚本,然后执行命令我们就可以实现我们想要的
首先我们可以执行这个命令在我们的组件文件夹下,比如我的是这个文件夹


image.png

首先我们安装fastlan 执行这个命令就行

sudo gem install -n /usr/local/bin fastlane

安装上fastlane
我们执行这个命令 用来初始化

fastlane init

这个时候会在这个文件下生成很多默认的文件


image.png

但是就我个人的习惯而言我会把他们全部删除,变成一个什么都没有的空文件,
然后我手动创建一个叫 fastfile的文本
比如这样子


image.png

然后我开始编辑脚本,比如我的脚本是这样的(也就是我fastfile文件中是这样写的)
desc 'ManagerLib  使用这个航道 可以快速的对自己的私有库进行创建,从而达到事半功倍的效果 '
# 整个程序的开始,到end 进行结尾
lane :ManagerLib do |options|

tagName = options[:tag]
targetName = options[:target]
# 需要执行的指令命令如下
# 1.pod install
# 一定要从根目录开始 ./ 当前的文件夹
    cocoapods(
      clean_install: true,
      podfile: "./Example/Podfile"
    )
# 2.git add .
    git_add(path: ".")
#  git commit -m 'xxxx'
    git_commit(path: ".", message: "我的提交内容")
#  git push origin master
    push_to_git_remote
# 3.git tag 标签名称
    add_git_tag(
      tag: tagName
    )
# 4. git push --tags
    push_git_tags
# 5. pod spec lint 远程验证
    pod_lib_lint(allow_warnings: true)
# 6. pod repo push DGFMSpecs 库的名字.podspec
    pod_push(path: "#{targetName}.podspec", repo: "DGFMSpecs",allow_warnings: true)
end

大致的介绍下 为什么要这些,其实我里面已经写了注释了,注释里面都是我要真正干的事情,其中ManagerLib 是我自己起的一个名字,options是一个参数的意思,你可以把他当成是字典,其中targetname 和 tagname 都是参数 ,外边传递过来的,下面我来解释下这个例子,比如我想实习下这个命令

git tag 标签名字

那么我可以这样找,在这个网站
https://docs.fastlane.tools/actions/
然后按照我图片的方式进行搜索

image.png

点击进去就可以看到这样的实现
image.png

这样我们就可以操作了,其他的命令也是一样,值得注意的是我们在这两个命令上一定要加上允许忽略,一定要这样写
image.png

否则报错可能会把你弄很久,本人就是被这个弄了很久很久,脚本我们写完了,那么我们来验证一下我们写的脚本是否正确吧 我们执行这个命令

fastlane lanes

如果成功,那么就会显示成功,如果不成功那么就是不成功的,他会告诉你错误在哪里然后你直接根据错误提示来进行修改就可以了。
然后我们开始执行命令来进行运行我们的脚本

fastlane ManagerLib tag:0.1.0 target:playerComponent

因为我的组件就叫playerComponent,我所创建的版本是0.1.0 所以我如上执行命令。
第一个小的操作完成了,但是有一个问题是 ,比如 我第二次在执行我同样的命令时候就会报错


image.png

原因是我本地以及远程已经有一个0.1.0 的tag了,那么我在此重新创建以及提交到远端的时候就会报错,所以我们需要完善我们的叫脚本,我们需要首先判断我们的脚本是不是存在这个tag然后我们在删除他,其中判断tag是否存在命令是有的 是这样的

#  判断此时的tag是否窜在
if git_tag_exists(tag: tagName)
  UI.message("这是一个打印信息,不用管 Found it ")
end

然后我们开始进行删除我们的tag,删除tag我们分为删除本地的和删除远程的tag,此时我们remove tag 发现actions 本身是没有的,但是可以看到一些git上一些人已经给写出来了,一中办法是我们可以使用Git上一些人写的,我们其实也可以自己去写,比如我们自己去写,我们需要执行代码如下,首先我们需要创建一个新的action
执行如下的代码

fastlane new_action
image.png

然后我们输入名字 remove_tag
可以看到我们的文件中多了个这个


image.png

然后我们开始编辑我们的rb文件,我的rb文件是这样的

module Fastlane
  module Actions
    module SharedValues
      REMOVE_TAG_CUSTOM_VALUE = :REMOVE_TAG_CUSTOM_VALUE
    end

    class RemoveTagAction < Action
      def self.run(params)
        
         # 先定义几个变量名字
         tagName = params[:tag]
         isRemoveLocalTag = params[:rL]
         isRemoveRemoteTag = params[:rR]
         # 找一个执行命令的数组
         cmds = []
         # 依次添加进数组里面去执行
         if isRemoveLocalTag
             cmds << "git tag -d #{tagName} "
         end
         
         if isRemoveRemoteTag
             cmds << " git push origin :#{tagName} "
         end
         # 开始执行数组里面的命令
         result = Actions.sh(cmds.join('&'))
         return result
         
         
         
      end

      #####################################################
      # @!group Documentation
      #####################################################

      def self.description
        "嗯 挺好的"
      end

      def self.details
        # Optional:
        # this is your chance to provide a more detailed description of this action
        "这是一个老夫废了很大神的东西才搞出来的"
      end

      def self.available_options
        # Define all options your action supports.

        # Below a few examples
        [
          FastlaneCore::ConfigItem.new(key: :tag,
                                     description: "需要删除的标签的名称",
                                     optional:false,
                                     is_string: true),
          FastlaneCore::ConfigItem.new(key: :rL,
                                     description: "需要删除的本地的标签名",
                                     optional:true,
                                     is_string: false),
          FastlaneCore::ConfigItem.new(key: :rR,
                                       description: "需要删除的远程的标签名",
                                       optional:true,
                                       is_string: false)
        ]
      end

      def self.output
        # Define the shared values you are going to provide
        # Example
        return nil
      end

      def self.return_value
        # If your method provides a return value, you can describe here what it does
      end

      def self.authors
        # So no one will ever forget your contribution to fastlane :) You are awesome btw!
        ["brown  shaoyeliu"]
      end

      def self.is_supported?(platform)
        # you can do things like
        #
        #  true
        #
        #  platform == :ios
        #
        #  [:ios, :mac].include?(platform)
        #

        platform == :ios
      end
    end
  end
end

其中

FastlaneCore::ConfigItem.new(key: :tag,
                                     description: "需要删除的标签的名称",
                                     optional:false,
                                     is_string: true),

这个是参数的传递,比如tag 传递参数tag,description:描述,optional:是否可以不填写的,否是必须要写的 is_string: 是否是字符串,我们弄完参数,会在这个地方找一个数组来存储我们的命令,最后在执行数组中的命令 ,这就是这段代码表示的意思

# 先定义几个变量名字
         tagName = params[:tag]
         isRemoveLocalTag = params[:rL]
         isRemoveRemoteTag = params[:rR]
         # 找一个执行命令的数组
         cmds = []
         # 依次添加进数组里面去执行
         if isRemoveLocalTag
             cmds << "git tag -d #{tagName} "
         end
         
         if isRemoveRemoteTag
             cmds << " git push origin :#{tagName} "
         end
         # 开始执行数组里面的命令
         Actions.sh(cmds.join('&'));

我们验证一下我们写的代码我们在这个目录下


image.png

然后执行这个命令

fastlane action remove_tag

发现是成功的 说明我们写的是正确的,然后我们在更改我们fastfile文件中命令,使之与remove_tag 命令关联,我们这个这样写

#  判断此时的tag是否窜在
if git_tag_exists(tag: tagName)
  UI.message("这是一个打印信息,不用管 Found it ")
  remove_tag(tag:tagName)
end

最后我们在执行这个命令

fastlane ManagerLib tag:0.1.0 target:playerComponent

发现是成功的

打包成framework 进行使用

1.我们接下来要做的事情是要修改podspec文件 让我们的组件搞成framework 方式进行集成,通过执行命令
首先我们要自己打一个framework ,比如我的代码和资源文件是这样的


image.png

其中有三个类是我的代码,其实还有一个图片文件,一般来说我们都是要搞一个资源文件,比如 resource.bundle 来存储图片,但是这里为啥不搞,因为我们是要在组件中打framework,如果我们也搞一个resource.bundle ,那么在真正运行的时候是不出现bundle 文件的,如果不出现bundle文件那么 我们在组件中图片是找不到,我们的目的是把我们的代码打成framework 但是不影响使用。具体是啥意思,比如我正常使用,我们点击这里


image.png

然后我们show in finder 然后显示包内容,可以看到这样的
image.png

可以看到有这样的一个东西 playerComponent.framework 这里面是有图片的,如果我们打成framework 并且包含resource.bundle 那么这里面就不会出现打包playerComponent.framework那么也找不到图片的地址,所以我们在打包成framework的时候 不能把资源搞成resource.bundle 这种,接下来我把我们打包的framework,放到我自己见的这个目录下
image.png

然后我们修改podspec文件,我们做如下的改动

if ENV['IS_SOURCE'] || ENV['playerComponent']
       s.vendored_frameworks = 'playerComponent/Products/playerComponent.framework'
   else
        s.source_files = 'playerComponent/Classes/**/*'
   end

整个的podspec文件是这样的

#
# Be sure to run `pod lib lint playerComponent.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
  s.name             = 'playerComponent'
  s.version          = '0.1.0'
  s.summary          = 'playerComponentxxx'

# 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: 描述阿说的好的阿斯顿啊稍等 阿斯顿啊稍等
                       DESC

  s.homepage         = 'https://github.com/liudiange/playerComponent'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '[email protected]' => '[email protected]' }
  s.source           = { :git => 'https://github.com/liudiange/playerComponent.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/'

   s.ios.deployment_target = '9.0'
   
   if ENV['IS_SOURCE'] || ENV['playerComponent']
       s.vendored_frameworks = 'playerComponent/Products/playerComponent.framework'
   else
        s.source_files = 'playerComponent/Classes/**/*'
   end
   s.resource_bundles = {
       'playerComponent' => ['playerComponent/Assets/*']
   }

  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'AFNetworking', '~> 2.3'
end

值得注意的是:不管是不是framework方式我们都是要把图片的路径这样写

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

具体原因上面都做过解释了
至于为什么pod 要这样写

if ENV['IS_SOURCE'] || ENV['playerComponent']
       s.vendored_frameworks = 'playerComponent/Products/playerComponent.framework'
   else
        s.source_files = 'playerComponent/Classes/**/*'
   end
   s.resource_bundles = {
       'playerComponent' => ['playerComponent/Assets/*']
   }

因为我想达到这样一种效果
当我执行这个命令的时候

IS_SOURCE=1 pod install --verbose

是安装的framework 形式,如果我执行

pod install --verbose

是普通的源码方式,pod可以随便传递参数的,我们为什么还要写上这个呢


image.png

因为我们整个工程中组件不止一个,比如我某一天想让就我这个组件成framework 其他的都是源码,那么我就可以这样执行命令

playerComponent=1 pod install --verbose

然后我们执行这个命令

IS_SOURCE=1 pod install --verbose

发现组件变成这样了


image.png

看不到源码的,整个包体也会更小点,编译也会更快,运行工程也不会报错。
然后执行这个命令

pod install --verbose

变成这个样子了


image.png

至此完美了
特别说明:其实有一个packmanager 可以自动化打包framework,但是我们为什么不用呢,因为比如podspec 包含 类似于

s.dependency 'AFNetworking', '~> 2.3'

那么用packmanager打包framework,就会包afn的源码打进去,这样的话包体会变大并且编译也会报错,所以个人建议还是手动打包,只要知道原理怎么整怎么对
代码位置
https://github.com/liudiange/playerComponent

jenkins自动化打包简单的说明:

我也没怎么弄过这个,一般都是自己直接打包给测试,如果大家想学习可以到这个网站
https://www.jianshu.com/p/d6fdd13a7201
网上的资料还有很多,自己查资料看下吧 这个东西个人觉得可以知道 也可以不知道 不是必须的。

总结:

我这篇文章只是总了一些市场上比较认可的方式,其中我觉得重点是组件化是怎么划分、中间件最重要的我觉得是pod 我上边那些知识,其实只要掌握那些知识,别人写的文章也就可以看得懂了。(关于以上pod 那些demo 在我的git主页上 https://github.com/liudiange/)

你可能感兴趣的:(组件化的理解与运用)