iOS組件化實踐

前言

公司業務不斷迭代擴張,項目的功能愈來愈多也愈來愈複雜,各個業務之間也不可避免的耦合愈來愈多,代碼也愈來愈臃腫,原來的模式已經沒法知足現有項目開發高複用、高可維護性的需求,目前業界解決業務多樣性複雜性比較好的一種架構思路就是組件化,將項目拆分紅各個模塊,這樣能很好的解決現有的代碼耦合度高、複用性不足的問題,也方便管理各個模塊。數組

技術選型

前期調研了一些組件化的方案,大體概括爲三個方案url-block、protocol-class、target-action,通過權衡後,咱們最終選擇target-action這種方案,說到這裏確定會有人問了大家是基於什麼理由去定的這個方案的呢?因爲三個方案的優缺點所有描述篇幅太大,下面我主要基於調用方式、傳參模式給你們闡述下。bash

調用方式

組件之間的調用其實就是調用方調用服務方的一個過程,這裏就涉及到一個問題調用方如何發現服務方的問題。微信

  • url-block是經過url來發現服務,服務方在應用啓動時候優先註冊一系列url和對應的block到內存中;
  • protocol-class實際上是基於url-block的一種擴展補充的組件化方案,服務方也須要在應用啓動的時候優先將class和protocol作一個映射存放到內存中;
  • target-action基於的是runtime,不涉及到任何註冊映射關係的問題。

那麼問題來了,url-block、protocol-class每次啓動的時候都須要註冊一系列的映射關係到內存,隨着項目的愈來愈龐大,不可避免的會消耗掉更多的內存;業務的擴展變動不可避免的會涉及到服務映射關係的維護(增刪改),維護成本也會不斷增長,target-action則經過runtime徹底避免了相似的問題,內存開銷小,維護成本低。網絡

傳參模式

組件間調用,不可避免的會涉及到參數的傳遞。架構

  • url-block是基於url,這樣弊端就暴露無遺了,url傳遞的參數限制性很強,就舉個簡單的列子,分享模塊的分享圖片問題,圖片之類的參數url傳遞不了,再好比字典、數組等,這樣url傳遞這些參數顯然不合適也不方便;
  • protocol-class正是因爲url-block傳參的弊端才孕育而生的擴展方法,這裏雖然能解決傳遞複雜參數的問題,可是內存增長和難維護的問題依舊存在;
  • target-action方案提出了去model化傳參,由於若是傳遞的是對應的model,由於對應的model通常都是和對應的業務或者模塊掛鉤的,這樣組件之間本質上仍是沒有獨立,沒有達到去耦合的目的,最終這個方案調用方和服務方之間是經過字典來進行傳參,這樣作具有字典傳參的靈活性、多樣性,也具有了url-block方案不具有傳遞複雜參數的能力,參數去model化和調用runtime也完全斬斷了服務方和中間件之間的依賴,真正意義上實現了組件化。

備註:本文咱們會不斷提到一個概念叫runtime(其主要特性是消息傳遞,若是消息在對象中找不到,就進行轉發),若是對iOS runtime不是特別瞭解,能夠先搜索瞭解下,方便後面理解文章。app

target-action組件化方案

方案架構

target-action組件化方案分爲兩種調用方式,遠程調用和本地調用。模塊化

a. 遠程調用經過AppDelegate代理方法傳遞到當前應用,調用遠程接口並在內部作一些處理,處理完成後會在遠程接口內部調用本地接口,以實現本地調用爲遠程調用服務。工具

b. 本地調用由performTarget:action:params:方法負責,但調用方通常不直接調用此方法,會經過一箇中間層Media層,Media層會提供明確參數和方法名的方法,在方法內部調用performTarget:方法和參數的轉換。組件化

方案思路

組件化完整的鏈路是調用方 => 中間件 => 服務方,這樣整個調用算是完成,下面從後二者的角色來闡述下大體的一個實現思路(調用方其實很簡單,字面意思你們都懂)。測試

一、中間件
TBJMediator(中間件)是基於CTMediator(target-action方案做者提供)的優化版本,基於CTMediator作了一些優化和容錯處理。首先中間件對外(調用方)暴露明確參數類型的方法,調用performTarget發現服務方對應的Target和Action,實現本地組件間的調用,實際是經過runtime(俗稱消息分發)發現服務方和服務方對應的方法。這裏你們能夠思考一個問題,每一個業務或者模塊全部的調用若是都寫在這個中間件中,幾十個甚至幾百上千個方法,勢必會對這個中間件的後期維護帶來極大的麻煩(埋坑),基於這樣的現實孕育而生了Category方案,根據每一個服務方業務,對應建立一個TBJMediator的Category(中間件分類),這樣每一個業務對外暴露的接口和這些Category一一對應,可是全部對外接口都根據業務分離。

二、服務方
服務方顧名思義服務的提供方,其實Target-Action這個方案名稱已經提早劇透了,每一個Target就是對應服務方提供的服務類,其中的每一個Action就是具體的某項服務。每一個組件能夠根據實際須要提供一個或者多個Target類,在Target類中聲明Action方法,TBJMediator經過runtime主動發現服務。

具體實施

組件化的目的就是爲了下降耦合,可是項目中不可能不存在耦合,換句話說項目中各個業務都是有必定的關聯性,咱們要作的就是不斷下降沒必要要的耦合,讓項目變的架構清晰明瞭。爲了能優先完成整個組件化方案,咱們將拆分的維度適當放寬,剝離各個基礎組件和業務組件,並保證每一個組件的獨立性。

具體劃分出基礎組件、基礎業務模塊、業務模塊。

  • 基礎組件主要包含業務徹底無關的一些UI控件、UI工廠類、基礎工具類、網絡請求等;
  • 基礎業務模塊包含分享模塊、插件模塊、以及基礎服務模塊等;
  • 業務模塊主要包含產品模塊、用戶模塊等。

每一個模塊都基於CocoaPods進行管理,並相互保持獨立,業務模塊相互之間的調用也均經過中間層去調用,相互之間沒有直接引用。在拆分層級過程當中須要注意,上層不能對下層有依賴,下層中不能包含上層的業務邏輯,對於項目中的公共資源和代碼,儘可能下沉到下層中。

技術實現(產品模塊爲例)

調用方在某處調用[[TBJMediator sharedInstance] performTarget:targetName action:actionName params:@{...}]向TBJMediator發起跨組件調用,TBJMediator根據得到的target和action信息,經過objective-C的Runtime轉化生成Target實例以及對應的Action,而後最終調用到目標業務。

調用方

// ViewController.h
#import "TBJMediator+TBJProdModul.h"

UIViewController *netLoanListVC = [TBJMediator getNetLoanListVCWithTypeId:typeId title:title];
[self.navigationController pushViewController:netLoanListVC  animated:YES];
複製代碼

TBJMediator分類(中間件)

// TBJMediator+TBJProdModul.h

+ (UIViewController *)getNetLoanListVCWithTypeId:(NSString *)typeId title:(NSString *)title;

//  TBJMediator+TBJProdModul.m
+ (UIViewController *)getNetLoanListVCWithTypeId:(NSString *)typeId title:(NSString *)title
{
    id typeIdArg = NilObj(typeId);
    id titleArg = NilObj(typeId);
    return [[TBJMediator sharedInstance] performTarget:@"ProdModul" action:@"getNetLoanListVC" params:@{@"typeId": typeIdArg, @"title":titleArg} shouldCacheTarget:NO];
}
複製代碼

服務方

// Target_ProdModul.h

- (UIViewController *)getNetLoanListVC:(NSDictionary *)params;
複製代碼

上面代碼中調用方只須要依賴TBJMediator+TBJProdModul,進而達到了調用某個產品列表的目的,咱們解耦的目的達到了。固然咱們也能觀察到,如今的數據傳遞是經過字典,沒有用model傳遞,這樣作避免了直接依賴model,避免model暴露給全部組件,並且字典傳遞參數很靈活,能夠傳遞各類想要的數據類型。固然字典傳遞參數也不是沒有缺點,爲了調用方清晰,當參數個數比較多的時候,方法會看上去比較冗長,並且還須要特別注意參數的非空判斷。

總結

模塊化拆分時候須要注意的點

1.合理的拆分粒度

一開始拆分的時候粒度要適中,粒度太細的話拆分很困難,俗話說拔出蘿蔔帶出泥,先將相對粗粒度的業務獨立的組件拆分出來,後續若是一個拆分完成的庫仍然比較臃腫的化,說明仍然存在細化拆分的餘地。

2.制定拆分計劃

前期將項目組件大體梳理一遍,制定一個合理的拆分計劃,制定詳細的總體規劃可以將一些前期不合理的依賴、不合理的維度暴露出來,提高後續拆分的效率。

3.拆分原則

在拆分層級過程當中須要注意,上層不能對下層有依賴,下層中不能包含上層的業務邏輯。對於項目中的公共資源和代碼,儘可能下沉到下層中。

模塊化後相比單項目的一些缺點

1.固然模塊化雖然有不少優勢,可是實際操做過程當中因爲CocoaPods上傳私有庫步驟繁瑣,若是每一個庫都是手動去上傳,就會比較費勁,仍是須要一些額外的腳本配合。

2.因爲涉及到打包編譯順序問題(CocoaPods維護的私有庫優先編譯),有些預編譯宏要格外注意,否則可能編譯後的代碼並非你想要的,可能編譯成了測試環境或者其餘測試環境的代碼。

3.另外每次上線以前app打包也必需要保證每一個模塊必須是最新的版本,相對單項目就沒有這個問題。

組件化目前也只是邁出了這一步,後期還有不少須要優化改進,也但願有更多的技術大咖能給出建議。

做者簡介

狄仁傑,銅板街 iOS 開發工程師,2013年12月加入團隊,目前主要負責 APP 端 iOS 平常開發。

更多精彩內容,請掃碼關注 「銅板街科技」 微信公衆號。
相關文章
相關標籤/搜索