本文閱讀時(shí)長(zhǎng)
45
分鐘规阀,依賴注入DI
是控制反轉(zhuǎn)IOC
的實(shí)現(xiàn)挣跋,通過依賴注入可以讓代碼實(shí)現(xiàn)松耦合,增強(qiáng)了代碼的可擴(kuò)展性和可維護(hù)性悬嗓,同時(shí)也便于進(jìn)行單元測(cè)試污呼。
本文主要介紹一下內(nèi)容:
- 什么是控制反轉(zhuǎn)?什么依賴注入包竹?
-
iOS
開發(fā)中幾種實(shí)現(xiàn)依賴注入的方式燕酷。 - 通過實(shí)際
Demo
演示依賴注入DI
在開發(fā)中的實(shí)際運(yùn)用。
控制反轉(zhuǎn)和依賴注入
控制反轉(zhuǎn)
控制反轉(zhuǎn)Inversion of Control(IOC)
不是一種技術(shù)周瞎,只是一種思想苗缩,一個(gè)重要的面向?qū)ο缶幊痰姆▌t,它能指導(dǎo)我們?nèi)绾卧O(shè)計(jì)出松耦合声诸、更優(yōu)良的程序酱讶,簡(jiǎn)而言之就是讓框架來掌控程序的執(zhí)行流程,以完成類實(shí)例的創(chuàng)建和依賴關(guān)系的注入彼乌,聽起來很抽象泻肯,還是結(jié)合例子來說明渊迁。
假設(shè)你是蝙蝠俠,你每天都從新聞?dòng)浾甙柛ダ椎孪壬抢铽@得早上的晨報(bào)來了解哥譚的新聞灶挟,盡管你是蝙蝠俠宫纬,只要阿爾弗雷德先生休假你就無法看報(bào)紙了,問題就是蝙蝠俠看報(bào)紙是依賴阿爾弗雷德先生的膏萧,為了避免出現(xiàn)這個(gè)情況漓骚,你直接聯(lián)系阿爾弗雷德先生的機(jī)構(gòu),即高譚出版社榛泛,為你提供報(bào)紙蝌蹂。在這種情況下你即可以通過阿爾弗雷德先生獲得報(bào)紙,也可以通過該機(jī)構(gòu)認(rèn)為的任何其他代理人獲得報(bào)紙曹锨,蝙蝠俠把送報(bào)的控制權(quán)從只依賴的個(gè)人反轉(zhuǎn)到了報(bào)社孤个。
struct Newspaper {
}
class NewspaperAgent {
let name: String
init(name: String) {
self.name = name
}
func giveNewspaper() -> Newspaper { }
}
struct HouseOwnerDetails {
let name: String
}
class House {
let newsPaperAgent: NewspaperAgent
let houseOwnerDetails: HouseOwnerDetails
init(houseOwnerDetails: HouseOwnerDetails, newsPaperAgent: NewspaperAgent) {
self.houseOwnerDetails = houseOwnerDetails
self.newsPaperAgent = newsPaperAgent
}
func startMorningActivities() {
let newsPaper = newsPaperAgent.giveNewspaper()
}
}
let houseOwnerDetail = HouseOwnerDetails(name: "Batman")
let newsPaperAgent = NewspaperAgent(name: "Alfred")
let wayneManor = House(houseOwnerDetails: houseOwnerDetail, newsPaperAgent: newsPaperAgent)
上面是阿爾弗雷德為蝙蝠俠在送報(bào)紙,蝙蝠俠看報(bào)紙得依賴阿爾弗雷德沛简。
class House {
let newspaperAgency: NewsAgentProvidable
let houseOwnerDetails: HouseOwnerDetails
init(houseOwnerDetails: HouseOwnerDetails, newspaperAgency: NewsAgentProvidable) {
self.houseOwnerDetails = houseOwnerDetails
self.newspaperAgency = newspaperAgency
}
func startMorningActivities() {
let newspaper = newspaperAgency.getNewsAgent(for: houseOwnerDetails).giveNewsaper()
}
}
protocol NewsAgentProvidable {
func getNewsAgent(for ownerDetails: HouseOwnerDetails) -> NewsaperAgent
}
class NewsAgency: NewsAgentProvidable {
let name: String
var agents: [NewsaperAgent] = []
init(name: String) {
self.name = name
}
func getNewsAgent(for ownerDetails: HouseOwnerDetails) -> NewsaperAgent {
// Get a news agent
}
}
let houseOwnerDetail = HouseOwnerDetails(name: "Batman")
let agency = NewsAgency(name: "Gotham Publications")
let wayneManor = House(houseOwnerDetails: houseOwnerDetail, newspaperAgency: agency)
現(xiàn)在蝙蝠俠要看報(bào)紙就不用找阿爾弗雷德齐鲤,可以直接說“喂,是哥譚報(bào)社嗎椒楣,我想要一份晨報(bào)”给郊,這時(shí)報(bào)社就會(huì)安排人將報(bào)紙送來,當(dāng)某個(gè)快送員請(qǐng)假時(shí)就可以安排其他人繼續(xù)送捧灰,這樣就消除了蝙蝠俠與阿爾弗雷德直接的依賴關(guān)系淆九,在編程中體現(xiàn)為松耦合。
基于控制反轉(zhuǎn)的理念毛俏,在編程中一個(gè)類只負(fù)責(zé)其主要的職責(zé)炭庙,其他的事情需要移到外面去并與他們形成依賴關(guān)系,不用在類的內(nèi)部直接形成依賴煌寇,通過抽象化可以實(shí)現(xiàn)依賴的互換性焕蹄,實(shí)現(xiàn)控制反轉(zhuǎn)有很多種方式,其中依賴注入DI
就是實(shí)現(xiàn)控制反轉(zhuǎn)的一種阀溶。
依賴注入
當(dāng)一個(gè)classA
請(qǐng)求它的environment
來加載另外一個(gè)classB
腻脏,這樣無法直接讓classA
使用另外一個(gè)classC
,通俗的講就是無法隨意的更換合作者淌哟,這樣導(dǎo)致單元測(cè)試無法進(jìn)行迹卢,一旦項(xiàng)目龐大辽故,代碼的可維護(hù)性和可擴(kuò)展性就很低徒仓,其實(shí)上面蝙蝠俠的例子已經(jīng)使用了依賴性注入。
以前音樂盒能播放的音樂都刻在了鼓上誊垢,要想聽不同的音樂只能更換股掉弛,音樂盒為
classA
症见,內(nèi)部的classB
為鼓;現(xiàn)在的iPod
則只需要一個(gè)USB
的接口就能實(shí)現(xiàn)不同音樂的播放殃饿,這里的接口就是抽象化的產(chǎn)物谋作,實(shí)現(xiàn)了依賴的互換性。
依賴注入的幾種方式
在依賴注入中通常會(huì)存在三個(gè)角色:
-
Injector : 實(shí)現(xiàn)依賴關(guān)系并與
Client
連接乎芳。 -
Dependency : 被
Client
注入的依賴遵蚜。 - Client : 因功能完整需要注入依賴的那個(gè)類。
現(xiàn)在有一個(gè)代碼如下:
struct DenpendencyImplementation {
func foo(){
// Does something
}
}
class Client {
init() {
let denpendency = DenpendencyImplementation()
denpendency.foo()
}
}
let client = Client()
上面這段代碼很明顯Client
在內(nèi)部依賴了denpendency
奈惑,在類里創(chuàng)建了實(shí)例吭净,只要初始化Client
時(shí)就會(huì)調(diào)用foo
這個(gè)方法,試想一下如何只對(duì)Client
這個(gè)類進(jìn)行單元測(cè)試肴甸?因?yàn)?strong>denpendency
和Client
已經(jīng)耦合在一起了寂殉,單元測(cè)試變得異常困難,為此需要引入依賴注入原在。
Constructor Injection
Constructor Injection
注入是最常用的一種方式友扰,直接將依賴關(guān)系通過構(gòu)造函數(shù)的參數(shù)進(jìn)行注入:
protocol Dependency {
func foo()
}
struct DependencyImplementation: Dependency {
func foo() {
// Does something
}
}
class Client {
let dependency: Dependency
init(dependency: Dependency) {
self.dependency = dependency
}
func foo() {
dependency.foo()
}
}
let client = Client(dependency: DependencyImplementation())
client.foo()
上面代碼中用構(gòu)造函數(shù)的參數(shù)將dependency
職責(zé)分離分出,并且利用協(xié)議進(jìn)行了抽象化庶柿,這樣只需要符合Dependency
協(xié)議的依賴都能初始化Client
村怪,同時(shí)利用Dependency
協(xié)議可以生成一個(gè)Mock
的denpendency
來注入到Client
進(jìn)行單元測(cè)試。
優(yōu)點(diǎn):
- 對(duì)封裝極為友好浮庐。
- 保證
Client
總是處于完整的狀態(tài)实愚。
缺點(diǎn):
- 依賴注入后時(shí)無法在改變。
- 當(dāng)超過3個(gè)依賴時(shí)兔辅,構(gòu)造函數(shù)將會(huì)因參數(shù)過多而狠惡心?? 腊敲。
Setter Injection
Setter Injection
這是其他語言所說的屬性注入或者方法注入,利用屬性賦值的方式注入:
protocol Dependency {
func foo()
}
struct DependencyImplementation: Dependency {
func foo() {
// Does something
}
}
class Client {
var dependency: Dependency!
func foo() {
dependency.foo()
}
// 或者調(diào)用此方法給屬性賦值
func setDenpendency(denpendency:Dependency) {
self.denpendency = denpendency
}
}
let client = Client()
client.dependency = DependencyImplementation()
client.foo()
為了防止依賴沒有注入時(shí)屬性值為空维苔,這里需要使用可選項(xiàng)碰辅,依賴采用屬性賦值的方式進(jìn)行了注入。
優(yōu)點(diǎn):
- 可以初始化
Client
之后在進(jìn)行依賴的注入介时。 - 利用可讀的屬性可以注入具有多個(gè)依賴關(guān)系的對(duì)象没宾,非常方便。
缺點(diǎn):
- 由于是屬性注入在封裝時(shí)不太友好沸柔。
- 當(dāng)未注入依賴時(shí)或者忘記注入依賴時(shí)
Client
將出去欠缺狀態(tài)循衰。 - 必須得使用可選項(xiàng)屬性
Interface Injection
依賴通常通過屬性注入的方式注入,由Injector
統(tǒng)一來處理不同類型的Client
褐澎,并且Injector
可以運(yùn)用不同的策略在Client
上会钝,聽起來十分抽象,還是上代碼:
protocol Dependency {}
protocol HasDependency {
func setDependency(_ dependency: Dependency)
}
protocol DoesSomething {
func doSomething()
}
class Client: HasDependency, DoesSomething {
private var dependency: Dependency!
func setDependency(_ dependency: Dependency) {
self.dependency = dependency
}
func doSomething() {
// Does something with a dependency
}
}
class Injector {
typealias Client = HasDependency & DoesSomething
private var clients: [Client] = []
func inject(_ client: Client) {
clients.append(client)
client.setDependency(SomeDependency())
// Dependency applies its policies over clients
client.doSomething()
}
// Switch dependencies under certain conditions
func switchToAnotherDependency() {
clients.forEach { $0.setDependency(AnotherDependency()) }
}
}
class SomeDependency: Dependency {}
class AnotherDependency: Dependency {}
依靠Client
遵守HasDependency
和DoesSomething
二個(gè)協(xié)議來實(shí)現(xiàn)不同的行為,當(dāng)然這里HasDependency
的協(xié)議只是用方法注入來給Client
注入依賴迁酸,其實(shí)還可以是其它實(shí)現(xiàn)先鱼;Injector
中的Inject
方法給不同類型(如何實(shí)現(xiàn)二個(gè)協(xié)議)的注入SomeDependency
這個(gè)依賴,而switchToAnotherDependency
這個(gè)方法則注入的是AnotherDependency
這個(gè)依賴奸鬓,這樣就實(shí)現(xiàn)了Injector
負(fù)責(zé)處理不容類型的Client
并能注入不同的依賴焙畔。
優(yōu)點(diǎn):
- 同樣支持初始化
Client
之后在進(jìn)行依賴的注入。 -
Injector
可以根據(jù)不同類型的Cilent
注入不同的依賴串远。 -
Injector
可以根據(jù)Client
實(shí)現(xiàn)協(xié)議的不同實(shí)現(xiàn)不同類型的Client
宏多。
缺點(diǎn):
- 仔細(xì)看
Client
其實(shí)都成了Injector
的依賴了。
依賴注入模式
依賴注入目前主要有三種模式澡罚,本文主要介紹的是Dependency Injection Container
注入容器模式绷落。
- Factory
- Dependency Injection Container
- Service Locator
Dependency Injection Container
簡(jiǎn)稱DI Container
主要用來注冊(cè)和解決項(xiàng)目中的所有依賴關(guān)系,管理依賴對(duì)象的生命周期以及在需要的時(shí)候自動(dòng)進(jìn)行依賴注入始苇。
項(xiàng)目實(shí)戰(zhàn)
項(xiàng)目演示采用了
swiftUI
砌烁,最終效果如上圖所示,通過Privacy preferences
頁(yè)面選擇相應(yīng)的隱私權(quán)限級(jí)別來控制個(gè)人profile
主界面的相關(guān)個(gè)人信息模塊的展示催式,下面會(huì)貼出主要代碼函喉,具體Demo
傳送門在此。
界面的搭建
import SwiftUI
struct ProfileView<ContentProvider>: View where ContentProvider: ProfileContentProviderProtocol {
private let user: User
// 2 利用Combine實(shí)現(xiàn)響應(yīng)式
@ObservedObject private var provider: ContentProvider
// 1 采用構(gòu)造方法的注入方式進(jìn)行依賴對(duì)象的注入荣月,同時(shí)依賴對(duì)象從容器中統(tǒng)一獲取
init(provider: ContentProvider = DIContainer.shared.resolve(type: ContentProvider.self)!, user: User = DIContainer.shared.resolve(type: User.self)!) {
self.provider = provider
self.user = user
}
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ProfileHeaderView(
user: user,
canSendMessage: provider.canSendMessage,
canStartVideoChat: provider.canStartVideoChat
)
provider.friendsView
provider.photosView
provider.feedView
}
}
.navigationTitle("Profile")
.navigationBarItems(trailing: Button(action: {}){
NavigationLink(destination: UserPreferencesView<PreferencesStore>()){
Image(systemName: "gear")
}
})
}
}
}
代碼解讀:
-
ProfileView
采用了構(gòu)造方法來注入User
實(shí)例和滿足ProfileContentProviderProtocol
協(xié)議的依賴對(duì)象管呵,此ProfileContentProviderProtocol
協(xié)議則是上面我們提到的抽象封裝,只要滿足此協(xié)議的對(duì)象都能注入到ProfileView
中哺窄,同時(shí)二個(gè)依賴對(duì)象由DIContainer
統(tǒng)一進(jìn)行調(diào)配捐下。 -
provider
這個(gè)依賴對(duì)象使用了@ObservedObject
這個(gè)屬性包裝器,當(dāng)其相關(guān)屬性值發(fā)生改變時(shí)萌业,swiftUI
會(huì)及時(shí)刷新UI
保持ProfileHeaderView
坷襟,friendsView
,feedView
和photosView
為最新狀態(tài)生年。
主內(nèi)容依賴對(duì)象
import Foundation
import SwiftUI
import Combine
// 利用協(xié)議進(jìn)行了依賴對(duì)象的抽象化提取婴程,只要滿足協(xié)議的對(duì)象都能作為依賴對(duì)象注入
protocol ProfileContentProviderProtocol: ObservableObject {
var privacyLevel: PrivacyLevel { get }
var canSendMessage: Bool { get }
var canStartVideoChat: Bool { get }
var photosView: AnyView { get }
var feedView: AnyView { get }
var friendsView: AnyView { get }
}
// 遵守協(xié)議的依賴對(duì)象
final class ProfileContentProvider<Store>: ProfileContentProviderProtocol where Store: PreferencesStoreProtocol{
let privacyLevel: PrivacyLevel
private let user: User
private var store: Store
private var cancellables: Set<AnyCancellable> = []
// 1 依賴對(duì)象內(nèi)部也采用了構(gòu)造方法的注入
init(privacyLevel: PrivacyLevel = DIContainer.shared.resolve(type: PrivacyLevel.self)!, user: User = DIContainer.shared.resolve(type: User.self)!,
store: Store = DIContainer.shared.resolve(type: Store.self)!) {
self.privacyLevel = privacyLevel
self.user = user
self.store = store
// 2 訂閱事件
store.objectWillChange.sink{_ in
self.objectWillChange.send()
}
.store(in: &cancellables)
}
var canSendMessage: Bool {
privacyLevel >= store.messagePreference
}
var canStartVideoChat: Bool {
privacyLevel >= store.videoCallsPreference
}
var photosView: AnyView {
privacyLevel >= store.photosPreference ?
AnyView(PhotosView(photos: user.photos)) :
AnyView(EmptyView())
}
var feedView: AnyView {
privacyLevel >= store.feedPreference ?
AnyView(HistoryFeedView(posts: user.historyFeed)) :
AnyView(RestrictedAccessView())
}
var friendsView: AnyView {
privacyLevel >= store.friendsListPreference ?
AnyView(UsersView(title: "Friends", users: user.friends)) :
AnyView(EmptyView())
}
}
代碼解讀
-
ProfileContentProvider
內(nèi)部也同樣用構(gòu)造方法注入了PrivacyLevel
,User
和Store
實(shí)例的依賴對(duì)象抱婉,依賴對(duì)象同樣由DIContainer
統(tǒng)一進(jìn)行調(diào)配档叔。 -
store
實(shí)例訂閱了事件,當(dāng)store
進(jìn)行了持久化存儲(chǔ)的改變時(shí)會(huì)受收到事件蒸绩,并讓遵守ObservableObject
協(xié)議的ProfileContentProvider
發(fā)出事件衙四,以便ProfileView
收到事件后刷新UI
。 -
canSendMessage
患亿,canStartVideoChat
传蹈,photosView
,feedView
和friendsView
全部采用了計(jì)算屬性進(jìn)行定義,根據(jù)傳入進(jìn)來的依賴對(duì)象進(jìn)行屬性值的設(shè)置卡睦。
隱私權(quán)限持久化存儲(chǔ)
import Combine
import Foundation
protocol PreferencesStoreProtocol: ObservableObject {
var friendsListPreference: PrivacyLevel { get set }
var photosPreference: PrivacyLevel { get set }
var feedPreference: PrivacyLevel { get set }
var videoCallsPreference: PrivacyLevel { get set }
var messagePreference: PrivacyLevel { get set }
func resetPreferences()
}
final class PreferencesStore: PreferencesStoreProtocol {
// 1 遵守了ObservableObject需要用@Published指明需要發(fā)布的屬性
@Published var friendsListPreference = value(for: .friends, defaultValue: .friend) {
// 2 屬性觀察器
didSet {
set(value: photosPreference, for: .friends)
}
}
@Published var photosPreference = value(for: .photos, defaultValue: .friend) {
didSet {
set(value: photosPreference, for: .photos)
}
}
@Published var feedPreference = value(for: .feed, defaultValue: .friend) {
didSet {
set(value: feedPreference, for: .feed)
}
}
@Published var videoCallsPreference = value(for: .videoCall, defaultValue: .closeFriend) {
didSet {
set(value: videoCallsPreference, for: .videoCall)
}
}
@Published var messagePreference: PrivacyLevel = value(for: .message, defaultValue: .friend) {
didSet {
set(value: messagePreference, for: .message)
}
}
func resetPreferences() {
let defaults = UserDefaults.standard
PrivacySetting.allCases.forEach { setting in
//forEach注意return的問題
defaults.removeObject(forKey: setting.rawValue)
}
}
// 本地持久化存儲(chǔ)
private static func value(for key: PrivacySetting, defaultValue: PrivacyLevel) -> PrivacyLevel {
let value = UserDefaults.standard.string(forKey: key.rawValue) ?? ""
return PrivacyLevel.from(string: value) ?? defaultValue
}
private func set(value: PrivacyLevel, for key: PrivacySetting) {
UserDefaults.standard.setValue(value.title, forKey: key.rawValue)
}
}
代碼解讀:
-
PreferencesStore
利用UserDefaults
提供的重置宴胧,設(shè)置和取值三個(gè)方法用來持久化個(gè)人的隱私設(shè)置漱抓。 -
PreferencesStore
遵守ObservableObject
協(xié)議表锻,采用了@Published
來對(duì)需要發(fā)布的屬性進(jìn)行包裝。并采用了屬性觀察進(jìn)行持久化的存儲(chǔ)乞娄。
隱私頁(yè)面構(gòu)建
import SwiftUI
import Combine
struct UserPreferencesView<Store>: View where Store: PreferencesStoreProtocol {
private var store: Store
// 1 構(gòu)造方法注入
init(store: Store = DIContainer.shared.resolve(type: Store.self)!) {
self.store = store
}
var body: some View {
NavigationView {
VStack {
PreferenceView(title: .photos, value: store.photosPreference) { value in
// 2 觸發(fā)屬性觀察瞬逊,進(jìn)行持久化存儲(chǔ)并利用@published發(fā)布事件
PreferenceView(title: .friends, value: store.friendsListPreference) { value in
store.friendsListPreference = value
}
PreferenceView(title: .feed, value: store.feedPreference) { value in
store.feedPreference = value
}
PreferenceView(title: .videoCall, value: store.videoCallsPreference) { value in
store.videoCallsPreference = value
}
PreferenceView(title: .message, value: store.messagePreference) { value in
store.messagePreference = value
}
Spacer()
}
}.navigationBarTitle("Privacy preferences")
}
}
struct PreferenceView: View {
private let title: PrivacySetting
private let value: PrivacyLevel
// 3 點(diǎn)擊按鈕執(zhí)行的閉包
private let onPreferenceUpdated: (PrivacyLevel) -> Void
init(title: PrivacySetting, value: PrivacyLevel, onPreferenceUpdated: @escaping (PrivacyLevel) -> Void) {
self.title = title
self.value = value
self.onPreferenceUpdated = onPreferenceUpdated
}
var body: some View {
HStack {
Text(title.rawValue).font(.body)
Spacer()
PreferenceMenu(title: value.title, onPreferenceUpdated: onPreferenceUpdated)
}.padding()
}
}
struct PreferenceMenu: View {
@State var title: String
private let onPreferenceUpdated: (PrivacyLevel) -> Void
init(title: String, onPreferenceUpdated: @escaping (PrivacyLevel) -> Void) {
_title = State<String>(initialValue: title)
self.onPreferenceUpdated = onPreferenceUpdated
}
var body: some View {
Menu(title) {
Button(PrivacyLevel.closeFriend.title) {
onPreferenceUpdated(PrivacyLevel.closeFriend)
title = PrivacyLevel.closeFriend.title
}
Button(PrivacyLevel.friend.title) {
onPreferenceUpdated(PrivacyLevel.friend)
title = PrivacyLevel.friend.title
}
Button(PrivacyLevel.everyone.title) {
onPreferenceUpdated(PrivacyLevel.everyone)
title = PrivacyLevel.everyone.title
}
}
}
}
代碼解讀:
- 同樣采用構(gòu)造方法來注入
store
這個(gè)持久化存儲(chǔ)的實(shí)例,依然是DIContainer
統(tǒng)一調(diào)配仪或。 - 按鈕點(diǎn)擊后利用閉包回傳确镊,將隱私的值利用
store
進(jìn)行持久化的存儲(chǔ),并利用@published
進(jìn)行事件的發(fā)布范删。
DIContainer注入容器的創(chuàng)建
import Foundation
protocol DIContainerProtocol {
func register<Component>(type: Component.Type, component: Any)
func resolve<Component>(type: Component.Type) -> Component?
}
final class DIContainer: DIContainerProtocol {
// 采用單例模式
static let shared = DIContainer()
// 禁止外界使用init初始化
private init() {}
// 用字典保存依賴對(duì)象
var components: [String: Any] = [:]
func register<Component>(type: Component.Type, component: Any) {
// 注冊(cè)
components["\(type)"] = component
}
func resolve<Component>(type: Component.Type) -> Component? {
// 取出準(zhǔn)備注入
return components["\(type)"] as? Component
}
}
代碼解讀:
- 采用單利模式創(chuàng)建
DIContainer
蕾域,并將init
初始化方法設(shè)為private
防止外界調(diào)用init方法進(jìn)行初始化。 - 利用字典來將注冊(cè)過后的依賴對(duì)象進(jìn)行存儲(chǔ)或者取出到旦。
所有依賴對(duì)象的注冊(cè)
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
typealias Provider = ProfileContentProvider<PreferencesStore>
//這里是在進(jìn)行依賴注入對(duì)象的初始化旨巷,利用容器進(jìn)行注冊(cè)
let container = DIContainer.shared
container.register(type: PrivacyLevel.self, component: PrivacyLevel.friend)
container.register(type: User.self, component: Mock.user())
container.register(type: PreferencesStore.self, component: PreferencesStore())
container.register(
type: Provider.self,
component: Provider())
let profileView = ProfileView<Provider>()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: profileView)
self.window = window
window.makeKeyAndVisible()
}
}
}
代碼解讀:
- 在使用依賴對(duì)象之前統(tǒng)一用
DIContainer
單例將上面所有代碼所有用到的依賴對(duì)象進(jìn)行注冊(cè)即可。 - 在用到依賴對(duì)象的地方添忘,從
DIContainer
取出依賴對(duì)象注入即可采呐。
總結(jié):
只要介紹了控制反轉(zhuǎn)的思想,同時(shí)對(duì)依賴注入的幾種方式和模式進(jìn)行了介紹搁骑,并比較了其優(yōu)缺點(diǎn)斧吐,并演示了DIContainer
這種模式在項(xiàng)目中的實(shí)際運(yùn)用,在項(xiàng)目中使用依賴注入能將代碼松耦合仲器,而且便于后期的維護(hù)煤率,同時(shí)能很方便的進(jìn)行單元測(cè)試,適合測(cè)試驅(qū)動(dòng)開發(fā)(TDD
)乏冀。