iOS 組件化實踐思考

組件化的應用背景和優點在此再也不贅述,下面咱們將從實踐的角度,討論一下如何應用組件化的思想,下面將以我本身的理解逐步展開,拋磚引玉。ios

哪些內容須要組件化

在個人理解中,一個項目能夠拆分爲如下幾種組件:swift

  • 基礎組件;
  • 功能組件;
  • 業務組件;

下面依次來解釋幾種組件的定義和規則。設計模式

基礎組件

  • 基本配置
    • 常量;
    • 宏定義;
  • 分類
    • 各類系統類的擴展;
  • 網絡
    • 對 AFN 的封裝;
    • 對 SDWebImage 的封裝;
  • 工具類
    • 文件處理;
    • 設備信息;
    • 時間日期處理;

基礎組件的含義就是最基礎的東西,每一個業務組件都有可能會使用到,基礎組件須要抽取的應該是相似上面的代碼,舉例來講,好比咱們定義了一個常量,表示接口的根路徑: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
    }
  }
}複製代碼

能夠想到,也會有不少的業務組件會使用到這個擴展。工具

功能組件

  • 控件
    • 彈幕;
    • 輪播;
    • 菜單;
    • 瀑布流;
  • 功能
    • 斷點續傳;
    • 音視頻處理;
    • GPUImage 封裝;

功能組件分爲可見和不可見兩種,可見的是控件,不可見的是功能。功能組件的做用顧名思義,就是實現了一個功能。組件化

業務組件

業務組件,也就是業務的具體實現了,好比一個 App 的骨架以下:post

  • 首頁;
  • 發現;
  • 個人;

首頁下又分爲這樣:測試

  • 側滑菜單;
  • Banner;
  • 熱門;

這裏的每一個部分,均可以稱爲業務組件。

三種組件的關係

三種組件的關係
三種組件的關係

基礎組件規則

基礎組件和基礎組件之間不該該產生依賴,好比咱們使用網絡請求組件,但願根路徑是一個默認參數,但能夠對外暴露和修改,像下面這樣:

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複製代碼

其中 .widthSCREENWIDTH ,都在基礎組件中,但基礎組件中不只僅是這些東西,若是依賴了基礎組件,就須要導入基礎組件中其餘無用的代碼,並且其餘人使用輪播圖組件,也須要導入基礎組件。

所以,在功能組件中,不建議依賴基礎組件,上面的代碼應該改爲這樣:

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 庫的形式。

組件之間的通信

  • 對外公開 API 接口;
  • 經過中間件的中轉;

上面咱們有兩個遺留的問題,概括爲組件之間的通信問題,下面就經過這兩個問題,討論一下組件之間的通信。

網絡請求默認參數

下面的思路就是暴露出 baseUrl 參數,經過中間件 NetWorkMWNetWork常量 兩個基礎組件組合,完成默認參數網絡請求的封裝。

// 基礎組件 - 常量
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)
}複製代碼

以上其實是怎麼樣把多個組件組合使用起來,這種組合是肯定的,還有一些是不肯定的,例若有一個組件的狀態改變了,我要讓其餘組件知道個人變化,可是我不知道都要告訴誰,怎麼辦?

眼珠一轉,對外暴露狀態變化,中間件在變化時發送通知。可是同時我想附帶一個模型過去,通知的接收方怎樣正確的使用這個模型呢?若是要使用模型,勢必要和發送通知的業務組件產生耦合,怎麼辦?

之後再辦,先埋個坑,這些場景咱們會在之後再講到。

組件分離的難點

組件分離的重點和難點也就是解耦,好比咱們如今負責一個項目,其中的一個業務或者功能,但願實現組件化,可是它依賴於項目中的其餘公共功能,該如何處理呢?這裏提供兩種思路:

  1. 拷代碼,簡單粗暴,擺脫依賴,對於一些不重要的工具方法,能夠直接拷貝到內部來使用;
  2. 把組件依賴的代碼先作一個 pod 庫,而後依賴這個 pod 庫;

上面講到的是代碼方面的依賴,還有一種狀況是功能方面的依賴,好比咱們有一個菜單,這個菜單涉及到網絡圖片的加載,那麼怎樣將這個菜單進行組件化呢?

  1. 使用 Block 或者代理,將網絡圖片加載這部分的職責交給外部控制;

舉例來講,像下面這樣:

// 業務組件 - 菜單
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 的依賴。

附加問題

以上的環節掌握了,應該能夠嘗試簡單的組件化了,可是問題沒完,還有哪些呢?

庫的升級維護

隨着項目的迭代,你負責的庫升級了,其餘的小夥伴們還在用上個版本的庫,怎麼辦?

各類路徑資源問題

咱們在本身的庫裏使用了 imageNamedmainBundle,可是小夥伴把咱們的庫拖過去後,這些路徑和咱們不是一個路徑,Assets.xcassets 跟咱們也不是同一個 Assets.xcassets,怎麼辦?

這些問題你能夠從這篇文章找到答案:你真的會用 CocoaPods 嗎?

相關文章
相關標籤/搜索