將高度複用的程式碼封裝到靜態庫中不僅可以將程式碼解耦,還可以提高程式碼的可維護性。筆者所在公司的iOS專案是使用模組化開發的,專案中大量的可複用程式碼都被封裝成靜態庫。
靜態庫的迴圈引用
設想這樣的一個場景
A Framework 引用 B Framework,而此時B Framework 需要使用到 A Framework 中的一個服務。但不幸的是該服務耦合 在A Framework 中,為了避免引用迴圈,我們需要重構 A Framework,並且將該服務遷移到另外一個 C Framework 中。
我相信在現實中,沒有程式設計師原意冒著風險去重構 A Framework 的程式碼和單元測試(有這個時間,老子還不如去多玩幾把玩者)。
使用Protocol 解決迴圈引用
在這裡筆者提供一種思路來幫助大家在無需重構現有程式碼的前提下,將 A Framework 中的服務暴露給 B Framework。
這裡的示例程式碼可以在文末的 Github 連結中下載到
準備工作
在我們主專案中,我們定義了一個 Color
服務
protocol ColorLayer {
func backgroundColor() -> UIColor
}
class ColorService: ColorLayer {
func backgroundColor() -> UIColor {
return .red
}
}
複製程式碼
這時候我們新建一個靜態庫,並命名為 BridgeTestFramework
在這個靜態庫中,我們有這樣的一段程式碼。
public class BridgeViewController: UIViewController {
let colorService = ColorService()
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = colorService.backgroundColor()
}
}
複製程式碼
由於 BridgeTestFramework
被主專案引用,而 BridgeTestFramework
不能引用主專案,故編譯器會給出錯誤。
在 BridgeTestFramework
中定義 Protocol
複製 ColorService
的 Protocol 到 BridgeTestFramework
中,別忘記小改一下這個 Protocol 的名字並將其訪問許可權更改為 public
。
public protocol BridgeTestFrameworkColorLayer {
func backgroundColor() -> UIColor
}
複製程式碼
接著我們重構一下 BridgeViewController
public class BridgeViewController: UIViewController {
let colorService: BridgeTestFrameworkColorLayer
public init(colorService: BridgeTestFrameworkColorLayer) {
self.colorService = colorService
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = colorService.backgroundColor()
}
}
複製程式碼
在這裡我們將屬性 colorService
的型別用 BridgeTestFrameworkColorLayer
代替。由於 BridgeTestFrameworkColorLayer
和 ColorService
所暴露出來的 interface 是一樣的,所以我們可以正常使用 backgroundColor()
這個方法。
在主專案中實現 Protocol
接著我們回到主專案的程式碼。為 ColorService
引入 BridgeTestFramework
,並擴充 ColorService
。
import BridgeTestFramework
protocol ColorLayer {
func backgroundColor() -> UIColor
}
class ColorService: ColorLayer, BridgeTestFrameworkColorLayer {
func backgroundColor() -> UIColor {
return .red
}
}
複製程式碼
由於 ColorLayer
與 BridgeTestFrameworkColorLayer
所定義的 interface 是一樣的,所以我們無需更改 ColorService
的具體實現。
使用 Protocol
接著我們嘗試在主專案中使用一下我們重構後的 BridgeViewController
吧。
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let bridgeVC = BridgeViewController(colorService: ColorService())
present(bridgeVC, animated: true, completion: nil)
}
}
複製程式碼
由於 ColorService
實現了 BridgeTestFrameworkColorLayer
這個協議,BridgeViewController
可以正常使用 ColorService
對 backgroundColor()
這個 interface 的實現。
小結
在這個小 Demo 中我們使用 Protocol 在兩個靜態庫之間搭起一座臨時的橋樑。藉助 Protocol 我們可以在避免迴圈引用的前提下,在已經存在從屬引用關係的靜態庫之間分享某些服務。
但是請牢記,好的設計模式才是避免出現這種迴圈引用的根本解決方案。 Demo 原始碼可以在這裡下載到。