組件化的應用背景和優點在此再也不贅述,下面咱們將從實踐的角度,討論一下如何應用組件化的思想,下面將以我本身的理解逐步展開,拋磚引玉。ios
在個人理解中,一個項目能夠拆分爲如下幾種組件:swift
下面依次來解釋幾種組件的定義和規則。設計模式
基礎組件的含義就是最基礎的東西,每一個業務組件都有可能會使用到,基礎組件須要抽取的應該是相似上面的代碼,舉例來講,好比咱們定義了一個常量,表示接口的根路徑:bash
let BASEMIRRORURL = "http://rest.mirror.xxxx.com/ios"複製代碼
那麼這個常量在 Home,List,Detail 都有可能會被引用,所以咱們將這種最底層的,最下一層的東西歸類到基礎組件。網絡
又好比分類和擴展,咱們給 UIView
的擴展定義一個計算屬性:ide
extension UIView {
var height {
set {
self.frame.size.height = newValue
}
get {
return self.frame.size.height
}
}
}複製代碼
能夠想到,也會有不少的業務組件會使用到這個擴展。工具
功能組件分爲可見和不可見兩種,可見的是控件,不可見的是功能。功能組件的做用顧名思義,就是實現了一個功能。組件化
業務組件,也就是業務的具體實現了,好比一個 App 的骨架以下:post
首頁下又分爲這樣:測試
這裏的每一個部分,均可以稱爲業務組件。
基礎組件和基礎組件之間不該該產生依賴,好比咱們使用網絡請求組件,但願根路徑是一個默認參數,但能夠對外暴露和修改,像下面這樣:
class NetWork {
func request(baseUrl: String = BASEMIRRORURL, path: String, param: [String:Any]) {
}
}
NetWork.request(path: "/g/login.server", param: param)複製代碼
這時,NetWork
就依賴了 常量
這個基礎組件,咱們若是使用 NetWork
基礎組件,還須要導入 常量
這個基礎組件,這是不該該的。
但爲了代碼的簡潔性,這樣的封裝又是必要的,那麼應該怎麼作呢?這個問題咱們下面會講到。
功能組件和基礎組件之間不該該產生依賴,好比咱們作輪播圖,會用到 UIView 的擴展
和 常量
,像下面這樣:
imageView.width = SCREENWIDTH複製代碼
其中 .width
和 SCREENWIDTH
,都在基礎組件中,但基礎組件中不只僅是這些東西,若是依賴了基礎組件,就須要導入基礎組件中其餘無用的代碼,並且其餘人使用輪播圖組件,也須要導入基礎組件。
所以,在功能組件中,不建議依賴基礎組件,上面的代碼應該改爲這樣:
imageView.frame.size.width = UIScreen.main.bounds.size.width複製代碼
或者直接複製代碼,將須要的基礎組件的功能,複製到功能組件當中。
同基礎組件同樣,功能組件和功能組件也不該該產生依賴,道理是同樣的,咱們使用一個功能,不該該將另外一個功能也導入進來。
基礎組件和功能組件都是爲業務服務的,所以業務組件能夠依賴於基礎組件和功能組件,快速的實現業務,可是業務組件和業務組件之間不該該產生依賴。
好比這樣一條業務線,咱們要求 發現
這個業務組件,點擊一條視頻,跳轉到 視頻播放器
:
func pushToPlayerVC(model: VideoModel) {
let vc = PlayerVC(videoModel: model)
navigationVC.push(vc)
}複製代碼
這時 發現
就對 視頻播放器
產生了依賴,若是將 發現
進行組件化進行剝離,能行嗎?不行。
其實這個問題和網絡請求使用默認參數封裝同樣,是組件與組件之間的通信問題,固然,這個問題咱們下面會講到,如今再提一下是爲了一下子往下寫的時候忘了填坑 ...
組件的內部應該使用設計模式劃分文件夾的結構,例如 MVVM 結構:
---- PlayerView
-- View
-- Model
-- ViewModel複製代碼
組件的外部應該是一個遠程私有 pod
庫,使用 CocoaPods 進行管理。
單獨的測試工程。
組件的集成應該像上面的圖同樣,基礎組件和功能組件互不依賴,製做遠程 pod
私有庫,業務組件依賴於這些 pod
私有庫開發,一樣製做成遠程 pod
私有庫,殼工程依賴於 CocoaPods 管理這些私有庫,完成整個項目。
固然還有另外的方式,好比將殼工程做爲主工程,組件建立爲子工程,這方式的缺點是子工程能夠修改,缺乏約束性,目錄結構也比較凌亂。
還有將組件製做爲 FrameWork
,殼工程中導入一個個 FrameWork
庫,這種方式我的感受比上一種好一些,可是在物理上,組件和殼仍是沒能作到分離。
所以,我我的仍是更傾向於 pod
庫的形式。
上面咱們有兩個遺留的問題,概括爲組件之間的通信問題,下面就經過這兩個問題,討論一下組件之間的通信。
下面的思路就是暴露出 baseUrl
參數,經過中間件 NetWorkMW
將 NetWork
和 常量
兩個基礎組件組合,完成默認參數網絡請求的封裝。
// 基礎組件 - 常量
let BASEMIRRORURL = "http://rest.mirror.xxxx.com/ios"
// 基礎組件 - 網絡請求
class NetWork {
func request(baseUrl: String, path: String, param: [String:Any]) {
}
}
//殼工程 - 網絡請求中間件
class NetWorkMW {
func request(baseUrl: String = BASEMIRRORURL, path: String, param: [String:Any]) {
NetWork.request(baseUrl: baseUrl, path: path, param: param)
}
}
NetWorkMW.request(path: "/g/login.server", param: param)複製代碼
這個思路是使用代理,對外暴露點擊事件,經過中間件,導入 視頻播放
業務組件,topVC
基礎組件,完成向 視頻播放
的跳轉:
// 業務組件 - 發現
func pushToPlayerVC(model: VideoModel) {
delegate?.pushToPlayerVC?(videoModel: model)
}
// 中間件 - 發現
func pushToPlayerVC(model: VideoModel) {
let vc = PlayerVC(videoModel: model)
topVC.navigationVC.push(vc)
}複製代碼
以上其實是怎麼樣把多個組件組合使用起來,這種組合是肯定的,還有一些是不肯定的,例若有一個組件的狀態改變了,我要讓其餘組件知道個人變化,可是我不知道都要告訴誰,怎麼辦?
眼珠一轉,對外暴露狀態變化,中間件在變化時發送通知。可是同時我想附帶一個模型過去,通知的接收方怎樣正確的使用這個模型呢?若是要使用模型,勢必要和發送通知的業務組件產生耦合,怎麼辦?
之後再辦,先埋個坑,這些場景咱們會在之後再講到。
組件分離的重點和難點也就是解耦,好比咱們如今負責一個項目,其中的一個業務或者功能,但願實現組件化,可是它依賴於項目中的其餘公共功能,該如何處理呢?這裏提供兩種思路:
pod
庫,而後依賴這個 pod
庫;上面講到的是代碼方面的依賴,還有一種狀況是功能方面的依賴,好比咱們有一個菜單,這個菜單涉及到網絡圖片的加載,那麼怎樣將這個菜單進行組件化呢?
舉例來講,像下面這樣:
// 業務組件 - 菜單
self.imageView.sd_setImage(with: url, completed: completed)複製代碼
那麼若是如今將它組件化,這個組件就要依賴於 SDWebImage
,咱們應該修改爲這樣:
// 業務組件 - 菜單
setImage?(for: imageView, completed: ImageLoadCompletedBlock)
// 中間件 - 菜單
menu.setImage = { (imageView, completed) in
imageView.sd_setImage(with: url, completed: completed)
}複製代碼
如今菜單就擺脫了對 SDWebImage
的依賴。
以上的環節掌握了,應該能夠嘗試簡單的組件化了,可是問題沒完,還有哪些呢?
隨着項目的迭代,你負責的庫升級了,其餘的小夥伴們還在用上個版本的庫,怎麼辦?
咱們在本身的庫裏使用了 imageNamed
、mainBundle
,可是小夥伴把咱們的庫拖過去後,這些路徑和咱們不是一個路徑,Assets.xcassets
跟咱們也不是同一個 Assets.xcassets
,怎麼辦?
這些問題你能夠從這篇文章找到答案:你真的會用 CocoaPods 嗎?