iOS開發(fā)中依賴注入Dependency Injection

本文閱讀時(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>denpendencyClient已經(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è)Mockdenpendency來注入到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遵守HasDependencyDoesSomething二個(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坷襟,friendsViewfeedViewphotosView為最新狀態(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)造方法注入了PrivacyLevelUserStore實(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传蹈,photosViewfeedViewfriendsView全部采用了計(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)乏冀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涕侈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子煤辨,更是在濱河造成了極大的恐慌裳涛,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件众辨,死亡現(xiàn)場(chǎng)離奇詭異端三,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鹃彻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門郊闯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事团赁∮Γ” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵欢摄,是天一觀的道長(zhǎng)熬丧。 經(jīng)常有香客問我,道長(zhǎng)怀挠,這世上最難降的妖魔是什么析蝴? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮绿淋,結(jié)果婚禮上闷畸,老公的妹妹穿的比我還像新娘。我一直安慰自己吞滞,他們只是感情好佑菩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著裁赠,像睡著了一般殿漠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上组贺,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天凸舵,我揣著相機(jī)與錄音剪芍,去河邊找鬼油啤。 笑死才写,一個(gè)胖子當(dāng)著我的面吹牛傲茄,可吹牛的內(nèi)容都是我干的童太。 我是一名探鬼主播移盆,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惰拱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼判没!你這毒婦竟也來了仪吧?” 一聲冷哼從身側(cè)響起庄新,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎薯鼠,沒想到半個(gè)月后择诈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡出皇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年羞芍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郊艘。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荷科,死狀恐怖唯咬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情畏浆,我是刑警寧澤胆胰,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站刻获,受9級(jí)特大地震影響蜀涨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜将鸵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一勉盅、第九天 我趴在偏房一處隱蔽的房頂上張望佑颇。 院中可真熱鬧顶掉,春花似錦、人聲如沸挑胸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)茬贵。三九已至簿透,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間解藻,已是汗流浹背老充。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留螟左,地道東北人啡浊。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像胶背,于是被迫代替她去往敵國(guó)和親巷嚣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容