美文网首页iOS && Androidios专题ios
iOS组件化(上篇)- 拆分基础组件

iOS组件化(上篇)- 拆分基础组件

作者: 我阿郑 | 来源:发表于2017-09-17 21:34 被阅读9211次

    修改说明:有朋友反映Xcode9编译RAC4.x失败,无法设置Use Legacy Swift Language Version 的问题。现在将RAC版本改成了2.5(OC版本)。感谢朋友们的反馈。git clone 之后执行pod install即可。不用再设置UseLegacySwiftLanguage Version。

          组件化在业界已经炒的水深火热,关于组件化的好处和组件化的方案网上已经有大篇的文章了。(Casa的这篇文章对几种方案的利弊讲述的很不错)笔者就不在赘述了。笔者通过拆分一个现有的demo来简单聊一下项目实施组件化的过程(将分为上篇、中篇、下三篇)。

    iOS组件化(上篇)- 拆分基础组件

    iOS组件化(中篇)-拆分业务组件

    iOS组件化(下篇)-加载XIB、图片资源

    demo可以从github下载(下载之后执行pod install ), 因为之前用RAC+MVVM写项目,这里用了RAC4.x。pod install之后需要修改一下ReactiveCocoaResult的UseLegacySwiftLanguage Version 为YES。按照下面的方式解决即可:

    修改Result 修改ReactiveCocoa

    设置好之后编译运行就ok了。(接口不太稳定,第一次运行可能没数据,再运行一次就ok了)

    demo采用了“去model化”的设计,关于“去model化”的优势《去model化和数据对象》讲述的很清楚。

    以下内容摘自《iOS应用架构谈 网络层设计方案》


    交付什么样的数据给业务层?

    我见过非常多的App的网络层在拿到JSON数据之后,会将数据转变成对应的对象原型。注意,我这里指的不是NSDictionary,而是类似Item这样的对象。这种做法是能够提高后续操作代码的可读性的。在比较直觉的思路里面,是需要这部分转化过程的,但这部分转化过程的成本是很大的,主要成本在于:

    数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。

    调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。

    同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。

    其实我们的理想情况是希望API的数据下发之后就能够直接被View所展示。首先要说的是,这种情况非常少。另外,这种做法使得View和API联系紧密,也是我们不希望发生的。

    在设计安居客的网络层数据交付这部分时,我添加了reformer(名字而已,叫什么都好)这个对象用于封装数据转化的逻辑,这个对象是一个独立对象,事实上,它是作为Adaptor模式存在的。我们可以这么理解:想象一下我们洗澡时候使用的莲蓬头,水管里出来的水是API下发的原始数据。reformer就是莲蓬头上的不同水流挡板,需要什么模式,就拨到什么模式。



    以下内容摘自《iOS应用架构谈 组件化方案》



    组件化方案中的去model设计

    组件间调用时,是需要针对参数做去model化的。如果组件间调用不对参数做去model化的设计,就会导致业务形式上被组件化了,实质上依然没有被独立

    假设模块A和模块B之间采用model化的方案去调用,那么调用方法时传递的参数就会是一个对象。

    如果对象不是一个面向接口的通用对象,那么mediator的参数处理就会非常复杂,因为要区分不同的对象类型。如果mediator不处理参数,直接将对象以范型的方式转交给模块B,那么模块B必然要包含对象类型的声明。假设对象声明放在模块A,那么B和A之间的组件化只是个形式主义。如果对象类型声明放在mediator,那么对于B而言,就不得不依赖mediator。但是,大家可以从上面的架构图中看到,对于响应请求的模块而言,依赖mediator并不是必要条件,因此这种依赖是完全不需要的,这种依赖的存在对于架构整体而言,是一种污染。

    如果参数是一个面向接口的对象,那么mediator对于这种参数的处理其实就没必要了,更多的是直接转给响应方的模块。而且接口的定义就不可能放在发起方的模块中了,只能放在mediator中。响应方如果要完成响应,就也必须要依赖mediator,然而前面我已经说过,响应方对于mediator的依赖是不必要的,因此参数其实也并不适合以面向接口的对象的方式去传递。

    因此,使用对象化的参数无论是否面向接口,带来的结果就是业务模块形式上是被组件化了,但实质上依然没有被独立。

    在这种跨模块场景中,参数最好还是以“去model化”的方式去传递,在iOS的开发中,就是以字典的方式去传递。这样就能够做到只有调用方依赖mediator,而响应方不需要依赖mediator。然而在去model化的实践中,由于这种方式自由度太大,我们至少需要保证调用方生成的参数能够被响应方理解,然而在组件化场景中,限制去model化方案的自由度的手段,相比于网络层和持久层更加容易得多。

    因为组件化天然具备了限制手段:参数不对就无法调用!无法调用时直接debug就能很快找到原因。


    本文中demo的大致结构如下:

    项目初始结构

    将一个项目组件化拆分掉,一般会拆分一些基础组件、一些功能组件和业务组件。将拆分好的组件放到远程仓库,统一通过Cocoapods进行管理。当然,要实现这个管理的过程,有一些概念还是必须知道的。

    概念图

    如上图所示: 远程索引库、本地索引库、远程代码库、本地代码库。笔者通过拆分demo 中的一个category的基础组件说明上面的四个概念。

    第一步:基础组件Category


    一、远程索引库

    什么是远程索引库?

            每创建一个组件都会带有一个 xxx.podspec 的索引文件。专门用来存放这些索引文件的库就叫做索引库。我们需要将这些索引文件上传到远程索引库才能保证其他的同事能够拿来用。

    创建远程索引库( 注:这里是在github上创建了一个public的organization名字叫FFComponent)笔者这里创建的public的,自己公司的项目创建private的私有索引库即可,私有索引的步骤和pubic的操作方式一样

    FFSpecs索引库

    远程索引库已经创建成功,可以看到远程索引库的地址

    索引库地址

    二、 本地索引库 (本地索引库就是用来存放本地索引文件的库)

    1. 打开终端 pod repo 查看一下当前有哪些本地索引库(如果你之前没有创建过,应该只有一个master)

    pod repo查看本地索引库

    2. 通过pod repo add <本地索引库的名字>  <远程索引库的地址> ,创建本地索引库并和远程索引库做关联(注:本地索引库的名字建议和远程索引库起的名字一样)

    命令

    3. 通过下面的方式可以查看本地索引库的物理地址

    物理地址

    三、远程代码库 (代码实际存放的远程仓库)

    创建远程代码仓库(和创建远程索引库的方式一样),创建一个FFCategoryKit的远程代码库,用来存放FFCategory组件的代码。同样获取到FFCategoryKit组件的远程代码库地址。

    FFCategory组件 远程代码库地址

    四、本地代码库

    创建FFCategoryKit组件本地代码库

    1. pod lib create <组件名>  创建本地代码组件模版库(根据自身需求对下面的提示信息做选择就好)

    2. 编译swift版本错误解决,修改一下ReactiveCocoa和Result的UseLegacySwiftLanguage Version 为YES。

    UseLegacySwiftLanguageVersion 为YES

    3 .编译运行通过看下效果。接着把FlowerField_Component 里的Others路径下的文件夹拖入到组件FFCategoryKit的classes路径下。

    4. 接着cd到Example下进行pod install (把刚才拖入到classes里的文件夹pod进来)

    5. 编译组件看是否报错,编译通过后需要修改podspecs索引文件,一般需要修改下面几个问题。

     a. 修改版本号

     b. 修改项目的简单概述和详细描述

     c. 修改homepage和source地址

     d. 添加依赖库

    修改前的状态如下图所示:

    修改前的状态

    修改对应的地方即可

    修改后如下:

    podspec修改后

    6. 编译运行通过后,提交组件到远程代码库并打tag.


     -  git add .

     - git commit -m “xxx"

     - git remote add origin 远程代码仓库地址

     - git push origin master

     - git tag 版本号 (注:这里的版本号必须和podspec里写的版本号一致)

     - git push --tags



    7. 通过pod spec lint - -verbose - -allow-warnings 命令验证podspec索引文件

    错误说明:如果遇到下面的错误,就按照提示在终端输入`echo "2.3" > .swift-version`,然后在继续验证,验证通过后再通过 pod repo push 提交到远程索引仓库,如果没有遇到该问题自动略过.

    错误

    8. 验证通过后,pod repo push <本地索引库> <索引文件名> - -verbose - -allow-warnings 提交索引文件到远程索引库。

    本地也可以查看已成功

    9. 接下来回到FlowerField_Component工程修改podfile文件,把FFCategoryKit组件pod进来(:需要在Podfile中指定组件远程索引库地址,如果不指定默认会从master的索引库查找就会报找不到组件)

    Podfile文件修改如下 :

    执行pod install,按照上面同样的方式修改一下ReactiveCocoa和Result的UseLegacySwiftLanguage Version 成YES

    同样在项目查看已经pod进来了。

    10. 编译运行如下

    第二步:基础组件APIs

            对项目FlowerField_Component中Others下的APIs实施组件化,因为APIs组件中会依赖AFN框架,所以这里会说明一下。按照和FFCategoryKit组件同样的方式来创建APIs组件对应的远程代码库,本地代码模版库,索引库就不用创建了。

    1. FFAPIs组件远程代码库和本地代码模版库

    FFAPIs组件远程代码库 本地代码模版库

    2. 同样将APIs文件夹拖入到FFAPIsKit下的classes下面。因为APIs中的NetworkHelper类依赖AFN框架,所以需要修改podspec索引文件,修改如下:

    3. 回到Example进行pod install, 编译运行通过后进行提交组件到远程代码库。按照同样的方式打好tag,验证podspec并提交索引文件。然后在本地索引库查看发现FFAPIsKit组件已经完成了。

    4. 最后,同样回到FlowerField_Component修改Podfile后pod install。修改ReactiveCocoa和Result的UseLegacySwiftLanguage Version 为YES后编译运行。

    5. 采用同样的方式对configs、reformerKeys、tools、mainView进行组件化,创建组件FFConfigsKit、FFReformerKeysKit、FFToolsKit、FFMainViewKit。

    说明:FFToolsKit 依赖MBProgressHUD库; 别忘记在对应的podspec索引文件中添加依赖库。

    6. 基础组件化结束之后,FlowerField_Component中Podfile如下:

    FlowerField_Component的Podfile

    首先感谢您能坚持听我啰嗦到这里。到这里上篇就结束了。后两篇将陆续更新出来。项目组件化过程中一定也会遇到各样的问题。欢迎大家留言交流互相学习。我的邮箱 jiajung@aliyun.com  也欢迎大家Emial交流。

    相关文章

      网友评论

      • _冰淇淋_:FFAPIsKit会依赖FFToolsKit(DBManager, FFHelper),在FFAPIsKit.podspec里添加依赖 s.dependency 'FFToolsKit' , 不行啊,怎么处理FFAPIsKit依赖FFToolsKit的情况呢?
      • 不辣先生:楼主,demo接口挂掉了,一直转菊花
      • __weiliang:楼主按照你的方法试了很多次了,pod spec lint 的时候在Example目录下总是找不到,来到根目录可以找到,但是添加到项目中pod install 后发现pod里是一个空文件夹,麻烦解决一下
      • 黛沁馨1990:编译报错,'FMDB/FMDatabase.h' file not found,在FFCategoryKit.podspec里已经有加入 # s.dependency 'FMDB',依然是报错,请问这个怎么解决呢
        黛沁馨1990:@水清_木秀 是的,不小心把注释符号包含了
        水清_木秀:把前面的#去掉
        黛沁馨1990:已经解决了,导入pod文件不对,感谢
      • 北冥风尘:一直在转菊花是什么情况呢、
      • zwing:楼主的kit是swift语言的?
      • 海浪萌物:去model话那个方式,假如其中有一个value为空的话,怎么防止项目崩溃呢?
      • Kesion:看了完三篇文章,我在想你对组件化是不是有点误解?
      • 生命回忆录:podspec索引文件的s.source_files配置我还有点疑问。我在Classes文件夹下不是直接放入类文件,而是放入一个有多个层级的文件夹,这时候s.source_files路径要怎么配置啊?
        黛沁馨1990:我也遇到这个问题了,请问下你是否已经解决了,能提供下解决办法么?
      • 生命回忆录:我有一个问题,如你上面所说,APIs中的NetworkHelper类依赖AFN框架,在pod install之后,编译会不会通不过啊?
      • 笑对人生2017:楼主你好,我想请教个问题:
        在组件化后,每个组件内所需要的网络请求,你是写在组件代码内了,还是怎样实现的呢?
      • coder_feng:运行的时候没有数据出来,一直在菊花转
      • 爱吃麦子的鱼:楼主,你的Demo加载不出来数据,更新一下呗:pray:
        黛沁馨1990:@JiaJung 依然加载不出来数据哦
        我阿郑:@_安然i 好的,好的,谢谢,最近太忙 我抽时间看下
      • Seven_lxy:demo pod缺少result 运行之后没数据
        我阿郑:@不叫蜡笔的小新 那个等着我更新一下 Xcode 升级后确实有这个问题 把podfile里面Rac版本降低到2.5就好了,等着更新下,谢谢
        Seven_lxy:@JiaJung 就是修改pod里面ReactiveCocoa和Result UseLegacySwiftLanguage Version 为YES的时候发现里面没有Result
        我阿郑:@不叫蜡笔的小新 没看懂您的问题 可以详细描述下吗
      • Everdinner:介绍的很详细,但是demo中Tools文件夹中的DBManager和FFHelper应该移到APIs文件夹中,否则会造成无法创建成功API组件
        咚咚咚oo:其实完全可以先把tools 组件化,然后让这个组件依赖tools 就不会有这个问题了
        Everdinner:@Super洁 是的,而且demo好像有许多问题,完成组件化后发现接口返回的数据不能被json格式化,报3840的错误,应该是服务器返回的不是标准的json格式数据,不知道你遇到了没有
        bc3d3e66fba3:是的,是不是FMDB也得依赖进去?
      • ikonan:你好,看了你的文章,学到了不少东西,但是我遇到了一个问题,把你FFApisKit 抽出来的时候,由于FFApisKit要依赖FFToolsKit( DBManager.h ) ,FFToolsKit 是我的本地仓库,加入FFApisKit的时候编译本地不会报错,但是验证FFApisKit.podspec 文件的时候回报FFAToolsKit 这个库通不过。
      • 3f58e786b546:先mark一下,然后再休闲的时候多看看:blush:
      • 向晚forever:您好!我这里按你的教程制作私有pod出现了一个问题
        我在Podfile 里添加:source 'https://github.com/houxingyu/HXYSpecs.git' 和 pod 'HXYCategoryKit'
        私有库github链接:https://github.com/houxingyu/HXYCategoryKit
        但是我pod install之后HXYCategoryKit文件夹里缺少category里面的分类。
        希望可以解答一下疑问,谢谢!
        笑对人生2017:@JiaJung 你好,我现在出现在示例代码中进行 pod install后,组件结构有,但是目录下为空。在创建所有组件中,只有FFCategoryKit,和FFReformerKeysKit出现这种问题。我的代码库地址:https://github.com/lingdf/FFSpecs
        向晚forever:@JiaJung 项目使用时。我根据你的文章操作一圈没有问题,但是我新建一个工程,执行pod install 。发现classes目录下代码没有下来。如果有时间你可以使用一下,看看原因。
        我阿郑:@向晚forever 你是在项目使用时,还是在组件中pod install 没有那
      • King_mr::flushed: 开始看的时候有点方,看完后觉得我是不是可以这样理解,就是说从一个螺丝上拧下一个螺母,然后放工具箱里,别人需要的时候直接拿出螺母拧到螺丝上,不需要再去换一个大小合适的螺丝?可以保证的是这个螺母是任何螺丝都能拧上去的?
        我阿郑:@King_mr 一个人开发可以利用这种思想,没必要都抽离成组件,但是一旦人多了,可以很短时间做抽离
        King_mr:@JiaJung :flushed: 个人开发用不上吧,团队用的才多吧
        我阿郑:@King_mr 基础组建,和功能性组建,可以这么理解,业务组建需要告诉外界你的一些信息,比如你这个螺母需要配什么型号的螺丝,就是需要对外提供一套可供其他业务组件通信的API
      • 半缘魔君:你好,看了你的组件化,我受益良;
        半缘魔君:@JiaJung 组件做好,用pod引入之后,工程里怎么导入?
        我阿郑:@半缘君换 谢谢,有帮助就好
      • 游城十代2dai:楼主你好~, 我是一个 iOS 新人, 对组件化第一次有这么深入一些的了解, 感谢楼主~
        有个问题, 就是这些组件, 无论是base 还是业务组件, 加入了 pods 的话定制性是不是要差了, base 还好说, 业务的那些, 比如图片选择, 实际开发的时候不同项目会有不同用法的
        我阿郑:@游城十代2dai 蒽蒽,是的,可以这么理解
        游城十代2dai:@JiaJung 相当于是每次遇到问题完善自己的组件是么? 然后...然后就像 afn 那种一个项目可能只用一个 get 或者 post, 而其他的功能用不上是吧...😂
        我阿郑:不同的项目有不同的用法,那就是需要对应组件对应对外提供可调用的API,这个没关系的。
      • c1482b09ec8a:还是楼主的文章通俗易懂,给个赞👍
        我阿郑:@jaccty 谢谢
      • iLeooooo:Xcode 9 运行不了 ~
        我阿郑:@DeathItachi 可以了,你重新克隆,pod install 就可以运行了
        iLeooooo:@JiaJung 我也是刚更新 打开提示我用Xcode8.x打开 因为Xcode 9已经没有swift2.3了~
        我阿郑:@DeathItachi 是嘛,我还没更新,等我看看呀,谢谢

      本文标题:iOS组件化(上篇)- 拆分基础组件

      本文链接:https://www.haomeiwen.com/subject/jhotlxtx.html