Swift 封裝篇

Swift 封裝篇

來自專欄 極光日報

簡評:在日益壯大的項目工程中,保持代碼的封裝性是一個很大的挑戰。隨著新功能的添加,對象通常會承擔新的職責,需要也其他的對象一起工作,有時新的改動可能會違背最初的設計,避免抽象泄露(leaking abstractions)不是一件容易的事情。

好的 API 規範可以幫助我們封裝代碼避免與其他類型共享不必要的實現細節。下面幾種方法是實現代碼封裝的常見方法。

隱藏實現細節

隱藏實現細節可以幫助我們減少不必要的錯誤,例如:我們構建了一個 ProfileViewController 類用於顯示當前登錄用戶的配置文件。它具有一個標題視圖(headerView),並且我們在 viewDidLoad 中設置 delegate,代碼如下所示:

class ProfileViewController: UIViewController, ProfileHeaderViewDelegate { lazy var headerView = ProfileHeaderView() override func viewDidLoad() { super.viewDidLoad() headerView.delegate = self view.addSubview(headerView) }}

這樣子咋一看沒有問題。但是假如你要將這個類給同事用,同事需要實現這樣一種功能:可讓用戶通過應用內購買來解鎖高級模式,這種模式下 headerView 需要變得更加酷炫。由於 headerView 沒有添加 private/fileprivate 修飾,所以這個 headerView 相當於暴露給了你的同事,他很可能用如下代碼實現這個 vip 功能:

func userDidUnlockPremiumSubscription() { profileViewController.headerView = PremiumHeaderView()}

當執行上面函數的時候原來的 headerView 已經丟失,並且新的 headerView 也沒有設置 delegate,這樣可能應用會卡在 ProfileViewController 這個界面,最後測試會直接找到你的頭上。

所以直接在介面上現在這種類情況,可以避免不必要的麻煩,這就是封裝的意義。

我們將上面代碼改動一下:

class ProfileViewController: UIViewController, ProfileHeaderViewDelegate { private lazy var headerView = ProfileHeaderView()}

這個時候你的同事找上門了,說內購成功後需要顯示 vip 的專屬界面。ok 我們只需要擴展原來的功能暴露一個 enterMode 方法即可:

extension ProfileViewController { enum Mode { case standard case premium } func enterMode(_ mode: Mode) { switch mode { case .standard: headerView.applyStandardAppearance() case .premium: headerView.applyPremiumAppearance() } }}

這你的同事只需要在內購成功回調中調用如下代碼即可:

func userDidUnlockPremiumSubscription() { profileViewController.enterMode(.premium)}

協議和私有類型

封裝代碼的另一個後方法是將協議和私有實現向結合。舉個例子:

加入我們正在構建一個需要在各種 ViewController 中載入大量圖像的 APP。為了解決這個問題,我們創建一個協議,該協議定義了 ImageLoader 我們 ViewController 可以訪問的 API:

protocol ImageLoader { typealias Handler = (Result<UIImage>) -> Void func loadImage(from url: URL, then handler: Handler)}

由於我們有很多的 ViewController 需要載入圖像,因此希望每個 ViewController 都是用自己的 ImageLoader,當 ViewController 銷毀的時候,需要取消未完成的請求。

為了很好的處理這個問題,我們使用工程模式構建一個 ImageLoaderFatory ,這樣可以方便的為每個 ViewController 構建 ImageLoader。ImageLoaderFatory 如下所示:

class ImageLoaderFactory { private let session: URLSession init(session: URLSession = .shared) { self.session = session } func makeImageLoader() -> ImageLoader { return SessionImageLoader(session: session) }}

我們注意到這裡並沒有 makeImageLoader 方法並沒有直接返回圖片載入器的具體類型,而是指明返回對象遵循 ImageLoader 這個協議。這樣我們就將 ImageLoader 具體實現隱藏起來了。

private extension ImageLoaderFactory { class SessionImageLoader: ImageLoader { let session: URLSession private var ongoingRequests = Set<Request>() init(session: URLSession) { self.session = session } deinit { cancelAllRequests() } func loadImage(from url: URL, then handler: (Result<UIImage>) -> Void) { let request = Request(url: url, handler: handler) perform(request) } }}

這樣做即給我們提供了很大的靈活性,同時也保持了API 非常簡明。

第三方依賴

上面的方法對封裝第三方庫也是非常的有用。

就像我們使用協議來隱藏具體的實現(SessionImageLoader)一樣,我們同樣可以使用協議來隱藏第三方庫,比如現在我們使用 AmazingImages 這個庫,我們可以用如下代碼進行封裝:

import AmazingImagesclass ImageLoaderFactory { func makeImageLoader() -> ImageLoader { return AmazingImageLoader() }}

這樣我們就只有一個文件使用到了 Amazing 這個庫,後續我們需要替換其他第三方庫也非常方便。

結論

項目越是複雜,應儘可能地封裝代碼---即使需要添加新功能,引入新的依賴關係或需求變更。這樣才能保持良好的體系結構。

原文:Code encapsulation in Swift

推薦閱讀:Swift 的 Enums 是 「Sum」 類型,這讓一切變得更有趣

極光日報,極光開發者旗下媒體。

每天導讀三篇英文技術文章。

推薦閱讀:

TAG:Swift語言 | iOS開發 | 科技 |