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

