iOS模块化的探索

作者: moubuns | 来源:发表于2017-04-26 00:14 被阅读1792次

    iOS原生模块化的探索

    大概是去年秋天开始,随着沪江学习的App越来越大,我做了很多模块化的尝试,最近要把沪学的一些轻量化学习的组件拆分并移植到CCtalk,所以把从去年的尝试写出来。

    我尝试过几种模块化的方式,可能在不同场景下使用不同的方式,带来的效果都不太一样。

    先说几个具体的例子

    练习

    exercise

    练习是第一个模块化的业务,起因如下:

    • 上一版的练习基于H5制作,网校和沪学共用一套,模块化能方便两条产品线接入使用
    • 练习没有课程的一些功能比如点赞
    • Swift2.0时代早期Xcode经常crash,OC和Swift混编会有一些问题,单独开发可以减少Xcode崩溃率😂
    • 沪学主项目已经很大了,在这样的工程下搞开发,开发体验太差,效率较低

    因为是第一个独立的模块,考虑了以下问题

    1. 是否使用SnapKit,TZStackView,RxSwift这类第三方库
      多引用一个库,意味着这个模块的依赖会复杂,从交互稿来看引入TZStackView是必要的,RxSwift和SnapKit对项目有模块侵入性太强,直接丢弃。
    2. 开发完成之后与主项目的集成是不是有坑(事实证明坑很大)
      刚开始也认为依赖很少坑应该比较小,集成的时候确实有大坑,会面会讲碰到了比较坑的问题。
    3. 队友是否接受这样开发?
      不接受,按这个方案做出来了,最后并没有使用模块的集成进去。等后期添加完口语题的的时候依赖就很复杂了,拆成单独的模块的难度指数增长。

    实现模块的时候,核心的问题是如何和主工程进行业务和数据的交互?先说明一个概念,我们的练习题的集合是一个课程,而题目和课程并不是一一对应的,题目是可以组合的,A课程和B课程都是有可能有相同的题目。
    所以在结构上大致的想法如图

    Module.002

    分成课程和内容

    课程可以 收藏,分享,点赞,打分,评价
    内容可以阅读,听写,做(练习),看(视频)

    做题这个模块,我划分的时候只应该包含内容部分,而课程部分属于业务逻辑,不同的App在计分的业务和出题的逻辑是不一样的。

    在这里我定义了一些接口,让业务方的Model实现这些接口,这样就有了做题的数据源。模块与业务逻辑使用接口来完成数据的转换,当子模块有数据返回给业务方的时,有模块内部抛出一个对业务方公开的Model。这样能保证内容与业务是完全独立的。还有一些小问题,比如一些课程需要的逻辑,做题的流程,使用一些通知来发出一些事件,业务逻辑根据通知来处理事件。

    /// 模块的接口
    /// 选择题类型
    public protocol ExerciseQuestionBody {
        var itemID: Int { get }
        var type: QuestionBodyType { get }
        var content: String { get }
        var imageURL: String { get }
        var audioURL: String { get }
    }
    
    ///主工程Model
    struct Question: ExerciseQuestionBody {
        let itemID: Int
        let type: QuestionBodyType
        let content: String
        let imageURL: String
        let audioURL: String
    }
    
    /// 模块的Model
    public struct SpeechAnswer: ExerciseUserSpeech {
        public let score: Float
        public let isCorrect: Bool
    }
        
    
    Module.001

    上面的图,是这种某块拆分的简单依赖关系,业务方依赖模块。

    总结

    优点

    • 可以复用
    • 方便测试
    • 开发过程愉悦

    缺点

    • 在做模块时需要考虑的比较完整
    • 集成到业务方需要做一些相应的对接

    在和App集成的时候遇到一个很大坑,因为我们的pods引用了静态库,!framework就不能使用了,练习模块是Swift开发同时有一个音频库的依赖,cocoapods引用音频播放库属于静态库,不能被Swift的framework链接...
    在做了很多尝试之后使用了比较trick的技巧,把音频库使用做为一个公共framework,让pods的Target去搜寻Carthage下是否有这个Framework。

    修改podSpec,让cocapods的target去查找framework

     s.vendored_frameworks = 'Carthage/Build/iOS/HSAudiomanager.framework'
     s.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '$(PROJECT_DIR)/../Carthage/Build/iOS/' }
    

    发现页

    discove

    当练习没有机会集成到主项目的时候,第二个机会来了,新版本的发现页,引用强运营需求的发现页的设计完全脱离了课程的体系,所以完全可以做为一个单独的子模块去实现。

    • 脱离主项目单独可以演示
    • 可以给公司其他产品使用
    • 页面元素组件化
    • 方便测试(为什么又提到测试)

    我又有了这些问题。

    1. 资源文件问题
      子模块里有自己的Loading,提供给调用方接口,去实现相应的样式。
    2. 发现页支持上拉加载更多,刷新,这类通用的逻辑是主项目中存在的,是否存在两套代码
      存在,因为如果要做为一个完整的框架,并且这几个逻辑属于必须的业务。
    3. 业务方使用发现页这个框架,如何更方便的定制,如果有我们写好的组件是否共享给别人
      我最初的想法就是,把发现页的模版单独做为业务逻辑模块,业务逻辑拥有自己的模版和数据适配器,根据自己的业务定制样式。在最近的其他产品线使用中,这个做法是验证可行的。
    Module.003

    这个是发现页目前的的结构

    因为有了之前的练习碰到的坑,发现页集成时可以说轻车熟路,碰到比较大的坑就是和OC交互的时候产生了问题 Framework 里不能写bridge桥接,而模版里有一些公共组件时Objective-C代码实现的,在重写不划算。所以OC的代码做为一个单独的Framework,在Swift框架只使用 Framwork依赖的方式解决。

    Slide 和 泛听

    slide extensiveListen

    Slide 是一个轻量级别的PPT,泛听是音频播放页面
    这两个模块并没有完全被抽离,因为有模块之间的耦合,Slide的PPT内容 和 泛听在音频介绍使用了同一个富文本渲染组件MediaView.

    Module.004

    那么就成了拆成两个模块 SlideKit 和 ListenKit 两个模块 同时引用一个MediaView组件模块

    查词模块

    查词属于一个比较老的代码,ListenKit 模块的拆分的时候发现了一些问题,有一些不必要的业务依赖,比如网络请求是写在内部的,添加的的依赖库实际上只使用了一个依赖库的一个函数。

    因为业务逻辑已经成熟,并且不会改动,我做了另一种模块化尝试,使用了依赖注入的方式,把项目中需要网络请求要求外部去实现。在内部有查词发音,发音的请求不需要通过验证,所以直接使用最基本URLSession实现。这样依赖注入的接口只有查词请求,和查词的Model,Model需要注入时原因是,因为可能查词来源会变,使用统一的借口,只需要适配不同的来源就可以了。

    Module.005

    AudioManager

    播放器组件,这是一个服务模块,属于比较底层服务。
    对于播放器最早的设想,给播放器URI就播放。所以早起播放器,只需要URL就可以了。
    当后期的时候有了播放列表,就扩展支持了播放列表的借口。
    播放列表需要包含如果信息,就提供一组播放列表的接口,让业务model去实现。

    Module.006

    模块化探索

    上面的几个例子,是我在去年模块化做的尝试中的一部分。
    目前我们有三种模块的话的方式

    1. 完全业务独立,这个模块开箱即用,只需要对项目的Router配置一下,就可以自动打开。因为这种组件的扩展性很低,所以我并没有去尝试。大概如下图的结构,其实等于每个App里包含了另一个App。


      Module.007
    2. 第二种方式类似查词模块,提供一组依赖注入的方式,由App实现,模块使用时调用App的Provieder,具有定制性,不过接口会很多。


      Module.008
    3. 第三种方式,模块化和组件分层,业务逻辑应当是由Component组合成的,Bussiness作为单独的一层。主App中只包含少量的公工业务逻辑。

    Module.009

    最后

    当然还是有第四种方式的,组件化React-Native

    待续...

    相关文章

      网友评论

      • 君赏:第三种方案和我之前写的如出一辙
      • Vinc:来看看大佬
        moubuns: @Vinc 😂😂😂

      本文标题:iOS模块化的探索

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