iOS资料汇总iOS Swift && Objective-C

iOS模块化的探索

2017-04-26  本文已影响1792人  moubuns

iOS原生模块化的探索

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

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

先说几个具体的例子

练习

exercise

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

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

  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

待续...

上一篇下一篇

猜你喜欢

热点阅读