Swift - 使用 Protocol 避免框架之間迴圈引用

Wenslow發表於2019-04-19

將高度複用的程式碼封裝到靜態庫中不僅可以將程式碼解耦,還可以提高程式碼的可維護性。筆者所在公司的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 代替。由於 BridgeTestFrameworkColorLayerColorService 所暴露出來的 interface 是一樣的,所以我們可以正常使用 backgroundColor() 這個方法。

在主專案中實現 Protocol

接著我們回到主專案的程式碼。為 ColorService 引入 BridgeTestFramework,並擴充 ColorService

import BridgeTestFramework

protocol ColorLayer {
    func backgroundColor() -> UIColor
}

class ColorService: ColorLayer, BridgeTestFrameworkColorLayer {
    func backgroundColor() -> UIColor {
        return .red
    }
}
複製程式碼

由於 ColorLayerBridgeTestFrameworkColorLayer 所定義的 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 可以正常使用 ColorServicebackgroundColor() 這個 interface 的實現。

小結

在這個小 Demo 中我們使用 Protocol 在兩個靜態庫之間搭起一座臨時的橋樑。藉助 Protocol 我們可以在避免迴圈引用的前提下,在已經存在從屬引用關係的靜態庫之間分享某些服務。

但是請牢記,好的設計模式才是避免出現這種迴圈引用的根本解決方案。 Demo 原始碼可以在這裡下載到。

相關文章