swift依賴注入和依賴注入容器詳解
什么是控制反轉(zhuǎn)(Inversion of Control)?
控制反轉(zhuǎn)就是把傳統(tǒng)的控制邏輯委托給另一個類或框架來處理,客戶方只需實現(xiàn)具體的任務(wù)而不需要關(guān)心控制邏輯。
舉個例子,比如存在客戶方和服務(wù)方兩個類,客戶方需要調(diào)用服務(wù)方的函數(shù)來執(zhí)行某個邏輯。在傳統(tǒng)的編程方式中,客戶方根據(jù)自己的需求直接去調(diào)用服務(wù)方的函數(shù)從而達(dá)到目的。而控制反轉(zhuǎn),則是把控制邏輯交給服務(wù)方,服務(wù)方提供了一個控制流的框架,具體的內(nèi)容需要由客戶方來填充,也就是說對流程的控制反轉(zhuǎn)了,現(xiàn)在是服務(wù)方調(diào)用客戶方。據(jù)說好萊塢有句名言 Don't call us, we'll call you,差不多就是這個意思。以上的服務(wù)方也可以是庫代碼或者框架。
在 iOS 開發(fā)中,有一個非常常見的控制反轉(zhuǎn)的實現(xiàn),可能很多人都沒有意識到這個就是控制反轉(zhuǎn),那就是 completionHandler,或者說 callback。
API.request(completion: { data in handleData(data) })
在這個例子中,業(yè)務(wù)方只需要關(guān)系拿到數(shù)據(jù)以后干什么,而不關(guān)心 completion 的調(diào)用時機(jī),把 completion 的調(diào)用委托給了網(wǎng)絡(luò)庫,這就是控制反轉(zhuǎn)。
控制反轉(zhuǎn)可以讓主要任務(wù)和控制邏輯分離,可以提升代碼的模塊性和擴(kuò)展性,讓代碼松耦合,并可以讓寫測試代碼變得簡單。
常見的控制反轉(zhuǎn)的實現(xiàn)有:
- 服務(wù)定 位 器(Service Locator)
- 依賴注入
- 上下文查找(Contextualized lookup)
- 模板方法(Template method)
- 策略模式(Strategy design pattern)
本文僅討論依賴注入這種實現(xiàn),暫不討論其他的實現(xiàn)。
什么是依賴注入?
依賴注入是控制反轉(zhuǎn)的一種具體實現(xiàn)。它在類的外部創(chuàng)建這個類的依賴對象,然后通過某個方式把這個依賴對象提供給類。通過依賴注入,把依賴對象的創(chuàng)建和綁定都移動到了類的外部。
先看下面的例子:
class Car { var tyres: [Tyre] init() { let tyre1 = Tyre() let tyre2 = Tyre() let tyre3 = Tyre() let tyre4 = Tyre() tyres = [tyre1, tyre2, tyre3, tyre4] } }
這個例子中構(gòu)建了一個汽車對象,汽車對象的構(gòu)建需要拼裝 4 個輪胎。這個代碼的缺點顯而易見,就是輪胎的創(chuàng)建邏輯和汽車本身耦合了。當(dāng)我們想換成另一種輪胎時,或者 Tyre 類調(diào)整了實現(xiàn)在構(gòu)造時添加了一個參數(shù),都必須改動 Car 類中的代碼。
用依賴注入的方式,把依賴對象的創(chuàng)建和綁定挪到類外部,就能解決這類問題。
class Car { var tyres: [Tyre] init(types: [Tyre]) { self.types = types } }
再舉個例子,App 開發(fā)中常見的網(wǎng)絡(luò)請求 -> 數(shù)據(jù)處理 -> 數(shù)據(jù)渲染流程,傳統(tǒng)方式開發(fā)如下:
// DataViewModel.swift func loadData() { API.requestData(id: 2222, completion: { data in self.handleData(data) }) }
這樣的代碼是無法測試的,因為 ViewModel 和具體的網(wǎng)絡(luò)請求實現(xiàn)耦合了。為了讓 loadData 這個方法可以被測試,應(yīng)該抽象一個網(wǎng)絡(luò)請求的接口,然后從外部注入這個接口的實現(xiàn)。如下代碼:
protocol Networking { func requestData(id: Int, completion: (Data) -> Void) }
讓 DataViewModel 擁有一個需要注入的屬性對象:
class DataViewModel { let networking: Networking init(networking: Networking) { self.networking = networking } }
loadData 方法修改如下:
func loadData(completion: (() -> Void)?) { networking.requestData(id: 2222, completion: { data in self.handleData(data) }) }
這樣把具體的網(wǎng)絡(luò)請求實現(xiàn)轉(zhuǎn)移到外部注入,在測試的時候就可以簡單的實現(xiàn)一個模擬的網(wǎng)絡(luò)請求,這樣就可以寫出測試代碼:
func testLoadData() { let networking = MockNetworking() let viewModel = DataViewModel(networking: networking) let expectation = XCTestExpectation() viewModel.loadData { expectation.fulfill() } wait(for: [expectation], timeout: .infinity) XCTAssertTrue(viewModel.data.xxxx) }
依賴注入最大的優(yōu)點就是實現(xiàn)了類之間的解耦。什么叫解耦,解耦就是兩個類之間雖然存在一些依賴關(guān)系,但是當(dāng)其中任何一個類的實現(xiàn)發(fā)生改變時,另一個類的實現(xiàn)完全不受影響,解耦本身是通過抽象接口來實現(xiàn)的,因此依賴注入也需要抽象接口,并且依賴注入將依賴的創(chuàng)建轉(zhuǎn)移到了客戶類的外部,把依賴的創(chuàng)建邏輯也和客戶類本身解耦。這樣無論是替換依賴對象的實現(xiàn)類,還是替換依賴對象類實現(xiàn)中的某個部分,客戶方類都不需要做任何修改。
依賴注入的種類
依賴注入主要通過初始化器注入、屬性注入、方法注入、接口注入等方式來進(jìn)行注入。
初始化器注入
初始化器注入就是通過初始化方法的參數(shù)來給對象提供依賴。初始化器注入是最常用的注入方式,它簡單直觀,當(dāng)一個對象依賴的對象的生命周期和對象自身相同時,使用初始化器注入是最好的方式。
class ClientObject { private var dependencyObject: DependencyObject init(dependencyObject: DependencyObject) { self.dependencyObject = dependencyObject } }
屬性注入
屬性注入是通過對象的公開屬性來給對象提供依賴,也可以叫 setter 注入。一般在無法使用初始化器注入時(例如使用了 Storyboard),或者依賴對象的生命周期小于對象時使用。
public class ClientObject { public var dependencyObject: DependencyObject } let client = ClientObject() client.dependencyObject = DefaultDependencyObject()
方法注入
方法注入的方式是對象需要實現(xiàn)一個接口,這個接口中聲明了能給對象提供依賴的方法。注入器通過調(diào)用這個方法來給對象提供依賴。方法注入也可以叫接口注入。
protocol DependencyObjectProvider { func dependencyObject() -> DependencyObject }
有時客戶方只在某些特定的條件下才需要使用依賴,這時可以用方法注入,客戶方僅僅在需要使用依賴的時候才會去調(diào)用方法來創(chuàng)建依賴對象,這樣避免了客戶方在不使用依賴的時候依賴對象也被創(chuàng)建出來占用內(nèi)存空間的問題。這一點也可以通過注入一個代碼塊來實現(xiàn):
init(dependencyBuilder: () -> DependencyObject) { self.dependencyBuilder = dependencyBuilder }
依賴注入容器
依賴注入要求對象的依賴在對象外部創(chuàng)建并通過某種方式注入到對象中。如果每次創(chuàng)建對象時都去創(chuàng)建一遍對象的依賴,會讓代碼變得重復(fù)和復(fù)雜,當(dāng)對象的依賴調(diào)整時,每個地方都需要做改動。因此通常使用依賴注入時,也需要一個依賴注入容器(Dependency Injection Container)。
依賴注入容器用來統(tǒng)一地管理依賴對象的創(chuàng)建和生命周期,也可以根據(jù)需要給對象注入依賴。
依賴注入容器要提供以下的功能:
- 注冊(Register):容器需要知道對于一個特定的類型,應(yīng)該怎樣構(gòu)建它的依賴,容器內(nèi)會保存類型-依賴的映射信息,并提供接口可以向容器添加這個類型信息,這個操作就是注冊。
- 解析(Resolve):當(dāng)使用依賴注入容器時,就不需要手動創(chuàng)建依賴了,而是讓容器幫我們做這件事情。容器需要提供一個方法來根據(jù)類型得到一個對象,容器會創(chuàng)建好這個對象的所有依賴,調(diào)用方即可無需關(guān)心依賴,直接使用這個對象即可。
- 處置(Dispose):容器需要管理依賴對象的生命周期,并在依賴對象的生命周期結(jié)束時處置它。
實現(xiàn)一個簡單的依賴注入容器
有很多第三方依賴注入框架實現(xiàn)了依賴注入的功能,例如 Swift 語言的 Swinject。我們也可以自己實現(xiàn)一個依賴注入容器。
按照依賴注入容器的定義,并借助 Swift 的泛型和協(xié)議,可以定義以下協(xié)議:
protocol DIContainer { func register<Component>(type: Component.Type, component: Any) func resolve<Component>(type: Component.Type) -> Component? }
具體實現(xiàn)如下:
final class DefaultDIContainer: DIContainer { static let shared = DefaultDIContainer() private init() {} var components: [String: Any] = [:] func register<Component>(type: Component.Type, component: Any) { components["\(type)"] = component } func resolve<Component>(type: Component.Type) -> Component? { return components["\(type)"] as? Component } }
有了這個 DIContainer,在使用時可以選擇兩種方式,一種是在外部 resolve 對象并注入。
let object = DIContaienr.shared.resolve(Data.self) let viewModel = ViewModel(dependencyObject: object)
一種是在初始化方法的參數(shù)默認(rèn)值中 resolve:
class ViewModel { init(dependencyObject: DependencyObject = DIContainer.shared.resolve(Data.self)) self.dependencyObject = dependencyObject }
這兩種方式各有一些使用場景,可以根據(jù)具體情況選用。
以上的 DIContainer 只是一個簡單的實現(xiàn),結(jié)合具體需求,可以添加上線程安全、Storyboard 注入,自動解析等功能,可參考 Swinject。
總結(jié)
依賴注入在很多領(lǐng)域都是常見的設(shè)計模式,例如 Java Spring 等,無論做哪個方向的開發(fā),依賴注入都是一定要掌握的。在合適的時候使用依賴注入,可以讓代碼解耦,提高代碼的可測試性、可擴(kuò)展性。
參考資料
以上就是swift依賴注入和依賴注入容器詳解的詳細(xì)內(nèi)容,更多關(guān)于swift依賴注入依賴注入容器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
簡陋的swift carthage copy-frameworks 輔助腳本代碼
下面小編就為大家分享一篇簡陋的swift carthage copy-frameworks 輔助腳本代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01Swift項目中利用SWRevealViewController實現(xiàn)側(cè)滑菜單
這篇文章主要介紹了Swift項目中利用SWRevealViewController實現(xiàn)側(cè)滑菜單,需要的朋友可以參考下2015-12-12Swift下使用UICollectionView 實現(xiàn)長按拖拽功能
拖拽排序是新聞類的App可以說是必有的交互設(shè)計,如今日頭條,網(wǎng)易新聞等。這篇文章主要介紹了Swift下使用UICollectionView 長按拖拽功能,需要的朋友可以參考下2017-03-03舉例講解Swift編程中switch...case語句的用法
這篇文章主要介紹了Swift編程中switch...case語句的用法,其中fallthrough關(guān)鍵字在switch語句中的使用是重點,需要的朋友可以參考下2016-04-04