Loading... # We Build SDK 或者 SDK > 写在第一版基于新的研发流程和架构的COS SDK发布之际 上半年的时间,我们在SDK的制作上面趟了不少坑,或许也埋下了不少坑。跌跌撞撞中也有一些小的经验。现在总结一下在制作一个的SDK道路上作出的一些尝试和那些解决了的问题。 ## 一个新的命题 制作SDK和之前写APP很大的不同首先来自于使用你产品的用户不一样了。APP都是终端用户在用,用户基本上都是和界面在交互。点点点,戳戳戳,摸摸摸。而SDK是开发者在用,他们的操作是:下载某某SDK,引入SDK,调用函数接口完成功能。使用的用户发生改变,我们的产品就不能简单的按照之前制作APP的一套标准来搞。 举个简单的例子:刚来的时候,发现我们在测试SDK的时候,测试的同学是在我们提供的Demo基础上进行测试,仍旧使用针对APP的那套测试方法,一个按钮点击之后是否符合预期。而比较尴尬的是,之前的SDK在放出去之后多多少少出现了一些质量上的问题。而针对于SDK比较标准的做法应该是UT(UnitTest)通过单元测试来保障SDK的质量。这就是区别。 在写APP的时候,我们还有UI/UE的同学帮我们去设计用户的交互界面。而SDK产品,我们就是UI和UE。需要我们自己去设计API(application program interface),把脸面先弄好。APP用户不关心你的代码写的好不好,而SDK用户则完全不一样:他们也是CodeFarmer。如果你的代码写的很挫,是会被骂的很惨的。而且将令对方开始怀疑你的产品质量,从而影响他们的决策要不要用你的产品。 做APP的时候我们可以比较容易的把需求拆解成很多个自需求,然后让不同的同学相对独立的去开发,而且很多基本上都是业务逻辑的开发。而SDK得制作,则显得 怎么说呢之前接触过的项目都有点像是小作坊生产。产品来了一个需求,然后leader就会把这个需求分配给A同学。剩下的就是A同学去完成了。然后下一个需求可能给了B同学,也可能给了C同学。他们之间从来没有探讨过完成这个需求对我们目前系统的影响,或者在目前的系统中有没有更好的解决方案。所以代码朝着很多方向“无序生长”。时间一长,就开始出现各种稀奇古怪的问题。但是只要业务和产品能够持续往前推进,代码的腐朽则显得不是那么那么重要。而一个SDK产品,如果你的代码都腐朽了那么你还剩下啥? .... 类似这样的问题不一而足。 而现在制作SDK,我们将面对一个新的命题:**如何制作一个很靠谱的SDK**。 > 或者一个最起码看起来很牛逼的SDK,如果装的时间久了,那么我们就有可能是一个很牛逼的SDK。 ## 怎么样一个靠谱的SDK 首先要解决的问题是如何定义**什么样才算一个靠谱的SDK?** 我无法给出一个准确的定义,但是对于什么样才算一个靠谱的SDK我有很多美好的想象。 一个靠谱的SDK应该首先能够完成他声称的功能。 一个靠谱的SDK应该是一个持续稳定输出的队友。不会中途挂机--Crash。坑死你 一个靠谱的SDK应该有足够的兼容性,在尽可能多的设备上跑的起来。 一个靠谱的SDK应该用起来很爽,接口简单明了,能够快速上手。粑粑啦啦能量!!! 一个靠谱的SDK应该没有那么多复杂的东西,一起看起来都很简单,简单就意味着好理解。 一个靠谱的SDK应该是有完备的文档,我可以跟着文档走就能使用起来了? 一个靠谱的SDK应该...... 好吧,是因为有太多的要求可以挂在这个“什么样才算一个靠谱的SDK”上面了,所以这里只能写一点点了。 ## 研发流程 流程在整个研发过程起到的作用,反正我是耳朵起茧子了。也就不赘述了。直接切入我们是做的。如下图:  主要分成了四大块: 1. 设计 2. 实施 3. 质量保障 4. 发布 我们在整体的研发流程当中,特意强调了“设计”的重要性。在一个新的SDK需求到达的时候,首先要进行架构和接口设计。给出这个SDK的"结构蓝图"。而后,我们会根据蓝图进行代码”实施“。当然在实施的过程中我们会依赖一些工具化的手段来保障代码质量,以及结构和接口还原的准确性。最后我们会有一道质量阀,当这个版本的SDK满足这个质量阀。就开始后面的发布流程。 如果你做过“数学建模”,可能会觉得这个流程很熟悉。首先针对问题抽象出模型,然后根据模型构建解决方案,之后验证好解决方案的正确性就可以发布了。 这是一个非常普通的研发流程,可能在任何一个书本上都能看到。但是从自己的工作经验来看,真正困难的是实施下来。因为中间会碰到一些坑啊。 ### 设计与架构很重要 > 在写代码的时候我们应该80%的时间在思考,20%的时间在Coding(甚至更少) #### 架构设计 ##### 模块化--处理代码的复用问题 关于模块化是个老生常谈的话题,基本上稍微大一点的工程都会采用模块化的方案。在iOS开发上cocoapods已经变成了事实上的模块化基础工具。因而,我们在实施模块化的时候,自然也采取了cocoapods作为基础工具链之一的方案。关于cocoapods基础概念就不多说了,它和其他语言的包管理工具npm、spm、pip等大同小异。主要解决了依赖管理和模块引入的问题。 我们借助cocoapods将模块拆成了一个个的podspec。然后使用本地相对路径引用的方式,来引入模块。比如我们的一个模块长这个样子: ~~~ ├── COSV4.podspec └── Pod └── Classes ├── COSClient.h ├── Request │ ├── ..... └── Util ├── COSClientDefine.h ~~~ 而在引入的时候,直接在Podfile中使用: ~~~ pod 'COSV4', :path => "../../Libs/COSV4" ~~~ 依照这种模块的管理方式我们将,产品拆成了各种基础模块和业务模块。而产品的主工程文件只是一个空壳。这种处理策略同时也避免了恼人的**xcodeproj工程文件冲突**问题。因为主工程文件主要是维护了一个Podfile文件来管理其依赖的模块的引用。 同时,这也很好的解决了我们同时维护多个产品时的代码复用。我们将所有的产品都放在了同一个git仓库中。起了一个看起来很唬人的名字“OneForAll”(来自于动漫《我的英雄学院》)。所有的模块或者产品通过最原始的“目录”方式进行隔离。 ~~~ ├── BisnessComponents (业务模块) │ ├── COSXML(每一个业务都是一个Podspec) │ ├── EnterpriseCloudDrive │ └── FaceInUI ├── Libs (公共基础库) │ └── COSV4 (每一个库都是一个Podspec) │ | ├── COSV4.podspec │ | └── Pod | |---.... ├── Products (产品,在该目录下面建立产品输出) │ └── COSV4DemoAppOld ├── README.md └── build.sh ~~~ 在Products目录下面放的就各种产品的工程配置,而其他目录下面则是拆成了podspec模块的基础类库或者业务。 ##### 多仓库VS单一仓库 先说结果,在经历了一系列的尝试、摸索、填坑之后,我们选择了单一仓库这种方式处理代码管理。 最开始,代码没有模块复用,都是堆在SVN的不同的目录下面。因为同时维护多个产品,里面充斥着大量的Contrl+C和Control+V的代码。为了避免这种情况,我们很自然的引入了模块化处理。我们使用原始的“发布更新”这种策略处理。每一个产品都是独立的仓库,而且每一个模块都是单独的仓库。我们建立了一个[内部的cocoapods的仓库](http://git.code.oa.com/qcloud-terminal/QCLOUDPodspecs)。在一个模块修改之后,发布到该仓库。然后其他的产品pod update拉取更新。 但是,立刻就陷入了维护的噩梦。很多基础类库都不是很稳定,经常变动,而且很多时候是在维护某个产品的时候,需要修改基础类库。这个时候,就得跑到另外一个工程修改基础类库,发布。然后再跑回来更新产品依赖。毕竟我们人力有限,把时间花在这种维护的事情上,实在是心痛。 于是立刻开始着手寻找解决方案,首先要解决的事同时开发多个类库的问题,尤其是同时修改开发调试基础类库。经过一圈研究我们使用了pod的本地相对引用的策略。决定了这个策略之后,剩下的一个问题是。怎么去管理其他的pod的git仓库。 在处理这个问题的时候,前期调研不足。首先上了gitsubmodule的方案来维护,最开始开心的用了一个月。但是时间一长就开始逐步的暴露问题:各仓库版本很难保持一致。具体解释一下这个问题。当我们A产品发布了V1.1.1版本的时候,我们会在A产品的git仓库上打上tagv1.1.1。为的是出现问题能够还原当时的代码环境修改问题。但是啊,很尴尬的是我们发现:他引用的gitsubmodules都没有打tag,而且没有地方记录v1.1.1其他submodule当时的commit信息。这就悲催了。 那么就要解决这个问题了,总不能无法修改问题吧。而android源码管理工具repo则正好解决了这个问题。在综合考虑了可能的一些潜在代码管理问题之后。认为repo能够很好的满足我们目前的需求:多git仓库版本一致性管理。于是我就开始了基于repo的改革。repo满足了基本的管理需求,但是发现还是有些不顺手的地方。于是在repo的基础上我构建了自己的工具[Hive](http://git.code.oa.com/qcloud-terminal/Hive)(欢迎围观,可能对于我们目前不适合,但是当团队规模扩大的时候,这套东西或许适合你们)。一个iOS多模块管理ToolKit。怎么说呢,当时写这个的时候。觉得还是很不错的,除了基本的git多仓库的管理功能。我甚至把git.gode.oa打通了。在创建模块的时候,会自动化的在git.code.ao上的创建对应的git仓库,还有把merge-reques的功能也脚本化了。为啥这么感慨,因为我现在切到单一仓库后,这套工具就暂时弃之不用了。也是心血啊。 为啥要切到“单一仓库”?因为管理模式更简单啊。你可能会问,你这么折腾其他同事怎么办。好吧,我很坦白的说,当我折腾到单一仓库的时候,产品基本上都是我一个人在维护。影响范围那是相当有限。而正是因为团队规模较小,才发现了一个问题:适合才是最重要的。hive的整套管理虽然付出了一些心血,但是发现多个git仓库管理对于团队规模较小而且核心库不稳定的情况非常的不适合。因为你要频繁的去维护多个不同的仓库。虽然使用hive就是敲一些命令的事情,但是终归是没有在同一个仓库中直接commit一下来的轻松啊。思前想后,为了降低管理和维护成本。痛下决心签到了单一仓库管理。 > 开心一刻:说点大家喜闻乐见的,我们团队目前虚位以待iOS和Android的兄弟,欢迎自荐或者推荐。 只是单纯的因为**“简单”**啊。 ##### GitFlow VS TBD 切到单一git仓库之后,那么必然要选取一个合适的分支管理策略。一般情况下有两个选择“GitFlow”或者“TBD(Trunk base develop)”。严格执行了一段时间的gitflow之后,发现还是啰嗦。关于为啥啰嗦,可以参考这篇文章:[Gitflow有害论](http://insights.thoughtworkers.org/gitflow-consider-harmful/)。总之就是你需要维护很多的分支,而且修改的代码无法及时同步到其他同事那里。这个时候我们有两个人了。 > 开心一刻:说点大家喜闻乐见的,我们团队目前虚位以待iOS和Android的兄弟,欢迎自荐或者推荐。 关于GitFlow就不多介绍了,着重说一下我们目前在使用的[TBD](https://trunkbaseddevelopment.com/)  * 一个软体开发的分支模型,也被称作mainline * 同一个产品开发的所有人员共享一个Repository,有一个trunk,单一Developer或是Developer团队可以有自己的private branch,所有修改最后都会回到主干 * 只有在Release时才会有官方的分支,一般Developer不能对Release Branch作动作,只有Release Engineer可以更动Release Branch,当Release Branch完成它的任务,就会被砍掉。 * **Google与Facebook都采用这种分支模型** 我们在使用上结合了gitflow的工具链,因为没有找到tbd相关的工具。gitflow则是一大堆。于是我们把master分支删掉了!是的删掉了。留下了develop分支作为trunk分支。然后所有的修改都在develop上进行。当发布或者过往版本修bug的时候,就用gitflow的工具创建一个小的临时分支。 一个没有持续集成和持续测试的代码管理不是一个真正的TBD。这一块在后面讲我们怎么去建立起来的。 ##### 怎样拆分模块 > 机制与策略分离 前面说到我们使用了模块化的策略,那么这就会自然引申出一个问题:你凭啥分的模块。简单点说就是:机制与策略分离。这是Linux设计哲学之一,所为机制就是能够被某一类需求公用的基础设施,所为策略则是这种基础设施的应用。举个形象点的例子: ~~~ f(x) = x x属于R ~~~ 就是一个机制,而 ~~~ f(1) = 1, x=1 ~~~ 就是一个策略。机制是对一类问题的归纳,是针对变化的归纳。之前想过这个问题写了一个[《iOS架构设计之冗余性思考》](http://km.oa.com/articles/show/315001),也是类似的思路。于是像网络处理、日志、文件基本管理、权限管理....这些都是基础机制,COS对象存储就是个策略。 所以要把这些基础的机制拆出来作为单独的podspec使用,因为他们通用性强啊。但是还会有一个粒度的问题。当然你可以把所有的基础功能类库,本着单一职责原则,拆的很细。网络一个库,甚至URLEncode一个库,日志一个库.....然后你就有了非常多的基础类库。然后噩梦就来了。东西越多你的管理成本就越大,虽然表面看起来符合了单一职责等设计原理。但是吧,维护是个噩梦啊。你还要考虑他们之间的版本用来。实话是:我这样干了,几乎拆除了几十个基础模块🤦♀️。然后又乖乖的把他们合到了“QCloudCore”里面。 > 简单的才是最好的 干完之后思考,其实模块的粒度并不是越细越好。需要适度的折中。在一个维护成本和设计原则中间取合适的中间值。 ##### 模块间通信 先定义一下模块间通信的概念:A模块对B模块产生了依赖则需要进行模块间”通信“。而这里所谓的依赖可以分为以下两个层次: 1. 接口依赖 2. 功能依赖 所谓接口依赖,就是我要引用你的头文件调用你的函数接口,比如打日志。所谓功能以来就是业务逻辑上处理的过程中,付款完了要跳转到另外一个页面,而这个页面是啥其实付款不需要关心。针对接口依赖的处理,这个没啥好的办法,只能乖乖的引用头文件。不过这里在引用的时候,我们不再推荐使用类似于pch这种全量引入的策略。而是用到啥引用啥。不要引用一堆不需要的东西,拖慢了编译速度。 而针对功能依赖这个就可以开始解耦了,我把自己之前写的一个[URLRoute](https://github.com/yishuiliunian/DZURLRoute)拖了进来。作为功能依赖的方案。解决依赖的资源的定位和转发的功能。 这是支持基于标准URL进行Native页面间跳转的Objective-C实现。方便您构页面之间高内聚低耦合的开发模式。他的核心思想是把每一个页面当成一个资源,通过标准的URL协议(统一资源定位符)来定位到每一个可触达的页面(资源)。 #### 处在流程中的架构设计和接口设计 上面说的是一些影响比较大的基础的技术方案。而接下来说处在最开始流程中的设计我么所做的事情。 在设计这块我们主要是做了两方面的设计: 1. 架构设计 2. 接口设计,也就是传说中的API 你可能会问,产品驱动跑哪里去了。好吧,有些时候技术类的产品只是高速你我要一个直播的SDK,然后剩下的就要靠你自己去想象了。很多时候,技术类的产品只会告诉你,我们当下需要什么功能。但是关于功能背后的稳定性、可扩展性、易用性等等,可能都不会有所设计。而这些就是我们在设计阶段要完成的事情。姑且把这个阶段的设计当成另外一种“产品需求文档”。 架构设计这个词吧,比较空泛。首先具化一下,就是盖房子的“建筑图纸”啊。要表明用什么材料,楼宇的承重结构之类的啊。所以在架构设计的时候,我们会强调两个东西: 1. 静态结构 2. 动态结构 所谓的静态结构就是类图,比如这样的:  这个具体的知识可以谷歌UML。他的主要作用是设计出一个SDK或者某块业务逻辑的静态结构:抽象出来的主要的类、类和类之间的关系。关于类和类之间的关系如何管理,这里有本书可以看看《元素模式》。基本上在下一阶段,代码实施的时候,主要的框架都是从这个图中来的。当然不排除一些修修改改。 我们在做这个事情的时候遇到的最大的问题是,我们之前都没有干过这个事情啊。什么UML了都是在现学。之前写APP业务逻辑,都是想到哪写到哪,很任性很随意的。而现在,把一部分时间从Coding中强制抽离出来,做设计的事情。刚开始的时候,战友们多少都有些抗拒。毕竟和之前的开发体验有些不一样。这个真的克服克服就好了。 而动态结构,则是程序跑起来会是什么样子。主要是一些流程图,数据流向图之类的。这块的东西倒是没静态结构强调的那么严。因为其实动态结构和具体的业务逻辑关系比较大。这个都是到了具体的技术细节的时候才会用,比如在管理某个链接的时候会要求出一个状态机:  我们发现,当强制从Coding中抽离出来的时候。对待需求的视野也顺便提高了一些,一些原先可能想的比较少的东西,开始变得重要起来。能够开始从一个宏观的角度思考的需求。将一些之前缺失的质量约束纳入进来,比如扩展性,比如代码的复用。当你被逼着思考这些问题的时候,这些问题才不会在Coding了N久之后被新来的同学吐槽。 ##### API SDK类产品API接口的可读性是非常重要的衡量SDK好坏的指标。无论你的功能多么牛逼哄哄,别人就是看不懂你的接口,也就无从谈起使用你的SDK。只说以下文字抄袭自[API设计原则](https://coolshell.cn/articles/18024.html)。自己重新写一边真的是没必要了。 **好API的6个特质** API之于程序员就如同图形界面之于普通用户(end-user)。API中的『P』实际上指的是『程序员』(Programmer),而不是『程序』(Program),强调的是API是给程序员使用的这一事实。 在第13期Qt季刊,Matthias 的关于API设计的文章中提出了观点:API应该极简(minimal)且完备(complete)、语义清晰简单(have clear and simple semantics)、符合直觉(be intuitive)、易于记忆(be easy to memorize)和引导API使用者写出可读代码(lead to readable code)。 ###### 极简 极简的API是指每个class的public成员尽可能少,public的class也尽可能少。这样的API更易理解、记忆、调试和变更。 ###### 完备 完备的API是指期望有的功能都包含了。这点会和保持API极简有些冲突。如果一个成员函数放在错误的类中,那么这个函数的潜在用户就会找不到,这也是违反完备性的。 ###### 语义清晰简单 就像其他的设计一样,我们应该遵守最少意外原则(the principle of least surprise)。好的API应该可以让常见的事完成的更简单,并有可以完成不常见的事的可能性,但是却不会关注于那些不常见的事。解决的是具体问题;当没有需求时不要过度通用化解决方案。(举个例子,在Qt 3中,QMimeSourceFactory不应命名成QImageLoader并有不一样的API。) ###### 符合直觉 就像计算机里的其他事物一样,API应该符合直觉。对于什么是符合直觉的什么不符合,不同经验和背景的人会有不同的看法。API符合直觉的测试方法:经验不很丰富的用户不用阅读API文档就能搞懂API,而且程序员不用了解API就能看明白使用API的代码。 ###### 易于记忆 为使API易于记忆,API的命名约定应该具有一致性和精确性。使用易于识别的模式和概念,并且避免用缩写。 ###### 引导API使用者写出可读代码 代码只写一次,却要多次的阅读(还有调试和修改)。写出可读性好的代码有时候要花费更多的时间,但对于产品的整个生命周期来说是节省了时间的。 最后,要记住的是,不同的用户会使用API的不同部分。尽管简单使用单个Qt类的实例应该符合直觉,但如果是要继承一个类,让用户事先看好文档是个合理的要求。 ##### 向优秀的设计看齐,我们做了下面的事情 ###### 代码规范 这是又一个之前过度流于口头的东西,但是我们就偏偏不信邪。讨论并书写了自己的代码规范,每一位同学都参与进来。然后互相监督。 1. [腾讯云终端Objective-C 编码规范](http://km.oa.com/group/30616/articles/show/298906) 2. [腾讯云终端Android编码规范建议](http://km.oa.com/group/qcloudternimal/articles/show/317367) ##### 接口层使用DSL自动化生成 当写到第二个SDK的时候,就开始发现。其实很大一部分都是针对于网络接口的封装。雷同的东西太多。而我又是一个非常懒得人,当同一件事情做第二次的时候,内心是抗拒的。也是就写了一个工具[Chameleon](http://git.code.oa.com/qcloud-terminal/Chameleon). 另外一点这样可以强制保持接口层的一致性。还极大的降低了出错的概率。之前写网络接口最大的尴尬就是入参的关键字拼错了,还费时费力的去调试。 > 通过API定义文件生成各语言版本SDK 【变色龙】可变成任何一种环境颜色 以COS XML的版本为例,只要根据我们自定义的一套简单的[DSL](http://git.code.oa.com/qcloud-terminal/Chameleon/blob/master/docs/intro.md)(真的是简单极了)书写网络接口层的描述文件。然后运行命令就可以了。 比如描述文件是这样的: ~~~ model XXXXResponse { string x [xx] } request Image (UP.url DOWN.json){ string pathxxx response XXXXResponse method xx/xxx server http://xxxxxx.com } ~~~ 然后运行命令: ~~~ chameleon oc request.model --outType=bwmodel -d . ~~~ 接口层的代码就自动生成了。关于具体的实现原理还是参考这个项目的介绍吧。  ##### 文档 ###### 注释 接口层尽量文档化,使用标注的apple的标注来写。他还支持markdown。把设计意图和接口功能描述清楚。 ###### 文档标准输出 接口文档化之后,一个好处就是你可以输出标准的apple的docset或者dash可以使用的docset。综合比较了各种文档生成工具之后,我们使用了[jazzy](https://github.com/realm/jazzy)作为文档输出工具。用以输出docset文档。用户导入之后就可以非常方便的查看各个类的使用。  ###### 接入CodeCC服务持续代码检查 所有的SDK产品都需要接入CodeCC进行静态代码检查。建立了一个叫做OneForAll的Xcode工程,将所有的模块都引入进来。以此为基础在CodeCC上对所有的代码进行静态检查。 http://codecc.oa.com ## 实施 这个环节都是一些琐碎的事情了,主要是围绕着Coding和CodeReview展开。我们没有使用任何强制性的CodeReview的工具。用的是滞后CR的策略。每一次提交git平台都会给项目组的成员发邮件,收到邮件之后,大家会去看一下。有任何问题或者不对的地方,通知修改者,修改后再提交一个commit。 ## 发布质量 > 一个牛逼的SDK 应该是什么样子?可能是那个样子吧。虽然现在不是那个样子,但是我们要先装的像那个样子,装的时间久了,我们就成那个样子了。 而整个流程的中的每一个环节都在做着质量保障的事情,无论是刚开始的架构和API设计,还是CR,还是代码规范。这是一个贯穿始终的话题。在这里单独摘出来说的就是自动化测试吧。 #### 单元测试与覆盖率 作为一个SDK,单元测试应该是一个必须的选项!!是的必须的选项。之前写demo让测试同学基于demo测试,发现其实很多问题都测不出来。质量一直得不到有效的保障。而单元测试就显得格外重要。 对于绝大部分的接口和业务场景,都写TestCase。同时异常和边界情况也可以去检查。以COS XML为例:  上图是我们跑的自动化测试的截屏。最开始我们是自己搭建了jenkins环境来跑自动化测试。但是后来发现公司有个[sdktest.oa.com](http://sdktest.oa.com/)平台,就将自动化测试迁移到这上面来了。 ### 打包&源码&发布 > 源码or not 源码,this is a qustion 作为SDK类产品,用户的使用方式会有多种多样,就拿iOS来说,用户可以使用以下方式: 1. 源码 2. 静态链接库 3. 动态链接库 4. cocoapods 5. Carthage 为了兼顾用户不同的引入方式,我们写了一个新的工具[qscaffold](http://git.code.oa.com/qcloud-terminal/qscaffold)。可以支持各种各样不同形式的产品打包输出。可以是framewrok,也可以是.a库文件。这个工具是在改造了的[cocoapods-packager-qcloud]([cocoapods-packager-qcloud]http://git.code.oa.com/qcloud-terminal/QRicker)基础上构建而来。 ~~~ qscaffold build -i podspecs.rclt -n QCloudCore ~~~ 一条命令可以将该库和库对应的依赖全部打包出来。 打包是发布的一种方式,主要是提供给希望直接使用编译成品的用户。而很多用户则希望能够使用cocoapods这样的工具来自动引入我们的产品,于是我们也会在cocoapods等标准的管理台上发布自己的产品。 #### 发布版本管理 因为我们采用了模块化的处理策略,那么在发布的时候,就会遇到发布多个模块的问题。一旦发布了多个模块,模块之间的版本的依赖关系就必须很好的维护起来。避免依赖地狱。这个问题是这样子的: 我们发布了产品A的1.0.0版本,此时A产品依赖B库的1.0.0版本。但是我们在写依赖的时候是用的宽松依赖: ~~~ A.dependency 'B' ~~~ 而在产品C的迭代过程中我们更新了B库到2.0.0版本,同时也发布了适配该调整的A产品1.0.0。 而用户测还是在使用A1.0.0这个时候,只要他`pod update`一下。将会拉取到: ~~~ A 1.0.0 B 2.0.0 ~~~ 然后就出现各种诡异的编译问题。而一旦出现这种问题,我们这边也很难还原用户当时的代码环境去调试问题。那么这里就得使用**严格版本依赖**这种策略。在每个模块发布的时候,对于每一个依赖关系都需要制定。 而这种口头的规定,在发布的流程中可能因为人为的疏忽就忘记掉了。于是在qscaffold工具里面加入了codeversion命令。来强制化处理版本。这个命令一方面会将podspec文件根据依赖关系转化成严格依赖的版本,一方面会在对应的模块中加入版本管理的代码: ~~~ #import <Foundation/Foundation.h> #import <QCloudCore/QCloudCoreVersion.h> #ifndef QCloudCOSXMLModuleVersion_h #define QCloudCOSXMLModuleVersion_h #define QCloudCOSXMLModuleVersionNumber 1001 //dependency #if QCloudCoreModuleVersionNumber != 1001 #error "库QCloudCOSXML依赖QCloudCore最小版本号为0.1.1,当前引入的QCloudCore版本号过低,请及时升级后使用" #endif // FOUNDATION_EXTERN NSString * const QCloudCOSXMLModuleVersion; FOUNDATION_EXTERN NSString * const QCloudCOSXMLModuleName; ~~~ 如果依赖的版本不对编译直接报错!!! > 死程序不说谎 #### 标准化发布流程 每一个产品的发布流程都写成文档样式的CheckList。每一次发布都跟着文档走。发布的时候,内容要包括这几个: 1. 代码 2. framework等库的集成方式 3. cocoapods 发布 4. 文档发布 同时还需要修改仓库中的CHANGLOG.md等。跟着CheckList走主要是为了防止在发布的过程中出错。导致发布出去的产品和我们开发的不一样。后面这块也在规划做成命令行的工具,加入到qscaffold的工具里面。 ## 标准化工具链 > 工欲上其事必先利其器 在整个过程中,我们构建了自己的一套工具链。如果你有类似的需求,可以围观一下,或者有好的建议可以提issues给我。 ### 纪念一下马上要被废弃的Hive 1. [Hive](http://git.code.oa.com/qcloud-terminal/Hive) ### 新的工具链qscffold和qricker 1. [cocoapods-packager-qcloud]() 2. [qricker](http://git.code.oa.com/qcloud-terminal/QRicker) 3. [qscafolld](http://git.code.oa.com/qcloud-terminal/qscaffold) 4. [Chameleon](http://git.code.oa.com/qcloud-terminal/Chameleon) > 开心一刻:说点大家喜闻乐见的,我们团队目前虚位以待iOS和Android的兄弟,欢迎自荐或者推荐。 最后修改:2020 年 11 月 17 日 12 : 04 PM © 允许规范转载 赞赏 如果觉得我的文章对你有用,请随意赞赏 ×Close 赞赏作者 扫一扫支付 支付宝支付 微信支付