iOS模块化的探索

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

待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 离我们家两个胡同的东边起第二家的那位老人去世了。 早上,我还没起床,就听见妈在说她的花被冻死了和老人不在了。 花我...
    东子金帛阅读 362评论 0 1
  • 2号的我擅长与别人沟通,喜欢聊天,讲话,是很好的配合与协调者^^ 可是长期的配合会失去自我,没有主见;在选择那方面...
    Sky星紫阅读 1,982评论 0 0
  • 日子一天天的过着 每个人都在一天天的变化 生理上 心理上 精神上 这种变化 是向积极的方面还是走下坡路呢 高三的时...
    123女孩阅读 361评论 0 1