iOS原生模塊化的探索
大概是去年秋天開始御蒲,隨著滬江學(xué)習(xí)的App越來(lái)越大纵顾,我做了很多模塊化的嘗試敬鬓,最近要把滬學(xué)的一些輕量化學(xué)習(xí)的組件拆分并移植到CCtalk串慰,所以把從去年的嘗試寫出來(lái)偏塞。
我嘗試過(guò)幾種模塊化的方式,可能在不同場(chǎng)景下使用不同的方式邦鲫,帶來(lái)的效果都不太一樣灸叼。
先說(shuō)幾個(gè)具體的例子
練習(xí)
練習(xí)是第一個(gè)模塊化的業(yè)務(wù),起因如下:
- 上一版的練習(xí)基于H5制作庆捺,網(wǎng)校和滬學(xué)共用一套古今,模塊化能方便兩條產(chǎn)品線接入使用
- 練習(xí)沒(méi)有課程的一些功能比如點(diǎn)贊
- Swift2.0時(shí)代早期Xcode經(jīng)常crash,OC和Swift混編會(huì)有一些問(wèn)題滔以,單獨(dú)開發(fā)可以減少Xcode崩潰率??
- 滬學(xué)主項(xiàng)目已經(jīng)很大了捉腥,在這樣的工程下搞開發(fā),開發(fā)體驗(yàn)太差你画,效率較低
因?yàn)槭堑谝粋€(gè)獨(dú)立的模塊抵碟,考慮了以下問(wèn)題
- 是否使用SnapKit,TZStackView坏匪,RxSwift這類第三方庫(kù)
多引用一個(gè)庫(kù)拟逮,意味著這個(gè)模塊的依賴會(huì)復(fù)雜,從交互稿來(lái)看引入TZStackView是必要的剥槐,RxSwift和SnapKit對(duì)項(xiàng)目有模塊侵入性太強(qiáng)唱歧,直接丟棄宪摧。 - 開發(fā)完成之后與主項(xiàng)目的集成是不是有坑(事實(shí)證明坑很大)
剛開始也認(rèn)為依賴很少坑應(yīng)該比較小粒竖,集成的時(shí)候確實(shí)有大坑,會(huì)面會(huì)講碰到了比較坑的問(wèn)題几于。 - 隊(duì)友是否接受這樣開發(fā)蕊苗?
不接受,按這個(gè)方案做出來(lái)了沿彭,最后并沒(méi)有使用模塊的集成進(jìn)去朽砰。等后期添加完口語(yǔ)題的的時(shí)候依賴就很復(fù)雜了,拆成單獨(dú)的模塊的難度指數(shù)增長(zhǎng)。
實(shí)現(xiàn)模塊的時(shí)候瞧柔,核心的問(wèn)題是如何和主工程進(jìn)行業(yè)務(wù)和數(shù)據(jù)的交互漆弄?先說(shuō)明一個(gè)概念,我們的練習(xí)題的集合是一個(gè)課程造锅,而題目和課程并不是一一對(duì)應(yīng)的撼唾,題目是可以組合的,A課程和B課程都是有可能有相同的題目哥蔚。
所以在結(jié)構(gòu)上大致的想法如圖
分成課程和內(nèi)容
課程可以 收藏倒谷,分享,點(diǎn)贊糙箍,打分渤愁,評(píng)價(jià)
內(nèi)容可以閱讀,聽寫深夯,做(練習(xí))抖格,看(視頻)
做題這個(gè)模塊,我劃分的時(shí)候只應(yīng)該包含內(nèi)容部分咕晋,而課程部分屬于業(yè)務(wù)邏輯他挎,不同的App在計(jì)分的業(yè)務(wù)和出題的邏輯是不一樣的。
在這里我定義了一些接口捡需,讓業(yè)務(wù)方的Model實(shí)現(xiàn)這些接口办桨,這樣就有了做題的數(shù)據(jù)源。模塊與業(yè)務(wù)邏輯使用接口來(lái)完成數(shù)據(jù)的轉(zhuǎn)換站辉,當(dāng)子模塊有數(shù)據(jù)返回給業(yè)務(wù)方的時(shí)呢撞,有模塊內(nèi)部拋出一個(gè)對(duì)業(yè)務(wù)方公開的Model。這樣能保證內(nèi)容與業(yè)務(wù)是完全獨(dú)立的饰剥。還有一些小問(wèn)題殊霞,比如一些課程需要的邏輯,做題的流程汰蓉,使用一些通知來(lái)發(fā)出一些事件绷蹲,業(yè)務(wù)邏輯根據(jù)通知來(lái)處理事件。
/// 模塊的接口
/// 選擇題類型
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
}
上面的圖顾孽,是這種某塊拆分的簡(jiǎn)單依賴關(guān)系祝钢,業(yè)務(wù)方依賴模塊。
總結(jié)
優(yōu)點(diǎn)
- 可以復(fù)用
- 方便測(cè)試
- 開發(fā)過(guò)程愉悅
缺點(diǎn)
- 在做模塊時(shí)需要考慮的比較完整
- 集成到業(yè)務(wù)方需要做一些相應(yīng)的對(duì)接
在和App集成的時(shí)候遇到一個(gè)很大坑若厚,因?yàn)槲覀兊膒ods引用了靜態(tài)庫(kù)拦英,!framework就不能使用了,練習(xí)模塊是Swift開發(fā)同時(shí)有一個(gè)音頻庫(kù)的依賴测秸,cocoapods引用音頻播放庫(kù)屬于靜態(tài)庫(kù)疤估,不能被Swift的framework鏈接...
在做了很多嘗試之后使用了比較trick的技巧灾常,把音頻庫(kù)使用做為一個(gè)公共framework,讓pods的Target去搜尋Carthage下是否有這個(gè)Framework铃拇。
修改podSpec钞瀑,讓cocapods的target去查找framework
s.vendored_frameworks = 'Carthage/Build/iOS/HSAudiomanager.framework'
s.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '$(PROJECT_DIR)/../Carthage/Build/iOS/' }
發(fā)現(xiàn)頁(yè)
當(dāng)練習(xí)沒(méi)有機(jī)會(huì)集成到主項(xiàng)目的時(shí)候,第二個(gè)機(jī)會(huì)來(lái)了慷荔,新版本的發(fā)現(xiàn)頁(yè)仔戈,引用強(qiáng)運(yùn)營(yíng)需求的發(fā)現(xiàn)頁(yè)的設(shè)計(jì)完全脫離了課程的體系,所以完全可以做為一個(gè)單獨(dú)的子模塊去實(shí)現(xiàn)拧廊。
- 脫離主項(xiàng)目單獨(dú)可以演示
- 可以給公司其他產(chǎn)品使用
- 頁(yè)面元素組件化
- 方便測(cè)試(為什么又提到測(cè)試)
我又有了這些問(wèn)題监徘。
- 資源文件問(wèn)題
子模塊里有自己的Loading,提供給調(diào)用方接口吧碾,去實(shí)現(xiàn)相應(yīng)的樣式凰盔。 - 發(fā)現(xiàn)頁(yè)支持上拉加載更多,刷新倦春,這類通用的邏輯是主項(xiàng)目中存在的户敬,是否存在兩套代碼
存在,因?yàn)槿绻鰹橐粋€(gè)完整的框架睁本,并且這幾個(gè)邏輯屬于必須的業(yè)務(wù)尿庐。 - 業(yè)務(wù)方使用發(fā)現(xiàn)頁(yè)這個(gè)框架,如何更方便的定制呢堰,如果有我們寫好的組件是否共享給別人
我最初的想法就是抄瑟,把發(fā)現(xiàn)頁(yè)的模版單獨(dú)做為業(yè)務(wù)邏輯模塊,業(yè)務(wù)邏輯擁有自己的模版和數(shù)據(jù)適配器枉疼,根據(jù)自己的業(yè)務(wù)定制樣式皮假。在最近的其他產(chǎn)品線使用中,這個(gè)做法是驗(yàn)證可行的骂维。
這個(gè)是發(fā)現(xiàn)頁(yè)目前的的結(jié)構(gòu)
因?yàn)橛辛酥暗木毩?xí)碰到的坑惹资,發(fā)現(xiàn)頁(yè)集成時(shí)可以說(shuō)輕車熟路,碰到比較大的坑就是和OC交互的時(shí)候產(chǎn)生了問(wèn)題 Framework 里不能寫bridge橋接航闺,而模版里有一些公共組件時(shí)Objective-C代碼實(shí)現(xiàn)的褪测,在重寫不劃算。所以O(shè)C的代碼做為一個(gè)單獨(dú)的Framework潦刃,在Swift框架只使用 Framwork依賴的方式解決侮措。
Slide 和 泛聽
Slide 是一個(gè)輕量級(jí)別的PPT,泛聽是音頻播放頁(yè)面
這兩個(gè)模塊并沒(méi)有完全被抽離福铅,因?yàn)橛心K之間的耦合萝毛,Slide的PPT內(nèi)容 和 泛聽在音頻介紹使用了同一個(gè)富文本渲染組件MediaView.
那么就成了拆成兩個(gè)模塊 SlideKit 和 ListenKit 兩個(gè)模塊 同時(shí)引用一個(gè)MediaView組件模塊
查詞模塊
查詞屬于一個(gè)比較老的代碼,ListenKit 模塊的拆分的時(shí)候發(fā)現(xiàn)了一些問(wèn)題滑黔,有一些不必要的業(yè)務(wù)依賴笆包,比如網(wǎng)絡(luò)請(qǐng)求是寫在內(nèi)部的,添加的的依賴庫(kù)實(shí)際上只使用了一個(gè)依賴庫(kù)的一個(gè)函數(shù)略荡。
因?yàn)闃I(yè)務(wù)邏輯已經(jīng)成熟庵佣,并且不會(huì)改動(dòng),我做了另一種模塊化嘗試汛兜,使用了依賴注入的方式巴粪,把項(xiàng)目中需要網(wǎng)絡(luò)請(qǐng)求要求外部去實(shí)現(xiàn)。在內(nèi)部有查詞發(fā)音粥谬,發(fā)音的請(qǐng)求不需要通過(guò)驗(yàn)證肛根,所以直接使用最基本URLSession實(shí)現(xiàn)。這樣依賴注入的接口只有查詞請(qǐng)求漏策,和查詞的Model派哲,Model需要注入時(shí)原因是,因?yàn)榭赡懿樵~來(lái)源會(huì)變掺喻,使用統(tǒng)一的借口芭届,只需要適配不同的來(lái)源就可以了。
AudioManager
播放器組件感耙,這是一個(gè)服務(wù)模塊褂乍,屬于比較底層服務(wù)。
對(duì)于播放器最早的設(shè)想即硼,給播放器URI就播放逃片。所以早起播放器,只需要URL就可以了只酥。
當(dāng)后期的時(shí)候有了播放列表题诵,就擴(kuò)展支持了播放列表的借口。
播放列表需要包含如果信息层皱,就提供一組播放列表的接口性锭,讓業(yè)務(wù)model去實(shí)現(xiàn)。
模塊化探索
上面的幾個(gè)例子叫胖,是我在去年模塊化做的嘗試中的一部分草冈。
目前我們有三種模塊的話的方式
-
完全業(yè)務(wù)獨(dú)立,這個(gè)模塊開箱即用瓮增,只需要對(duì)項(xiàng)目的Router配置一下怎棱,就可以自動(dòng)打開。因?yàn)檫@種組件的擴(kuò)展性很低绷跑,所以我并沒(méi)有去嘗試拳恋。大概如下圖的結(jié)構(gòu),其實(shí)等于每個(gè)App里包含了另一個(gè)App砸捏。
Module.007 -
第二種方式類似查詞模塊谬运,提供一組依賴注入的方式隙赁,由App實(shí)現(xiàn),模塊使用時(shí)調(diào)用App的Provieder梆暖,具有定制性伞访,不過(guò)接口會(huì)很多。
Module.008 第三種方式轰驳,模塊化和組件分層厚掷,業(yè)務(wù)邏輯應(yīng)當(dāng)是由Component組合成的,Bussiness作為單獨(dú)的一層级解。主App中只包含少量的公工業(yè)務(wù)邏輯冒黑。
最后
當(dāng)然還是有第四種方式的,組件化React-Native
待續(xù)...