版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2021.01.02 星期六 |
前言
今天翻閱蘋果的API文檔觅赊,發(fā)現(xiàn)多了一個框架SwiftUI责静,這里我們就一起來看一下這個框架角撞。感興趣的看下面幾篇文章贮乳。
1. SwiftUI框架詳細解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細解析 (二) —— 基于SwiftUI的閃屏頁的創(chuàng)建(一)
3. SwiftUI框架詳細解析 (三) —— 基于SwiftUI的閃屏頁的創(chuàng)建(二)
4. SwiftUI框架詳細解析 (四) —— 使用SwiftUI進行蘋果登錄(一)
5. SwiftUI框架詳細解析 (五) —— 使用SwiftUI進行蘋果登錄(二)
6. SwiftUI框架詳細解析 (六) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(一)
7. SwiftUI框架詳細解析 (七) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(二)
8. SwiftUI框架詳細解析 (八) —— 基于SwiftUI的動畫的實現(xiàn)(一)
9. SwiftUI框架詳細解析 (九) —— 基于SwiftUI的動畫的實現(xiàn)(二)
10. SwiftUI框架詳細解析 (十) —— 基于SwiftUI構(gòu)建各種自定義圖表(一)
11. SwiftUI框架詳細解析 (十一) —— 基于SwiftUI構(gòu)建各種自定義圖表(二)
12. SwiftUI框架詳細解析 (十二) —— 基于SwiftUI創(chuàng)建Mind-Map UI(一)
13. SwiftUI框架詳細解析 (十三) —— 基于SwiftUI創(chuàng)建Mind-Map UI(二)
14. SwiftUI框架詳細解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)
15. SwiftUI框架詳細解析 (十五) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(二)
開始
首先看下主要內(nèi)容:
在本教程中,您將在
SwiftUI
中創(chuàng)建社交媒體應(yīng)用的個人資料頁面時涤伐,學(xué)習(xí)有關(guān)iOS的Dependency Injection
的信息。內(nèi)容來自翻譯罪既。
接著看下寫作環(huán)境
Swift 5, iOS 14, Xcode 12
接著就是正文啦腹暖。
程序員開發(fā)了許多體系結(jié)構(gòu)汇在,設(shè)計模式和編程風格。 雖然它們各自解決不同的問題脏答,但它們都有助于使代碼更具可讀性糕殉,可測試性和靈活性。
Inversion of Control 因其效率而廣受歡迎殖告。 在本教程中阿蝶,您將通過Dependency Injection, or DI模式應(yīng)用此原理。 您無需編寫第三方框架黄绩,而是編寫自己的小型Dependency Injection
解決方案羡洁,并使用它來重構(gòu)應(yīng)用程序并添加一些新功能。
如果您不知道IoC
或DI
的全部含義爽丹,那么沒問題筑煮,您將很快了解更多。
注意:這是涉及
SwiftUI
的中級iOS教程粤蝎。 如果您不熟悉SwiftUI
真仲,請查看SwiftUI視頻課程SwiftUI video course。
打開入門項目初澎。 打開入門項目并運行該應(yīng)用程序:
您會從社交媒體應(yīng)用程序中看到一個配置文件屏幕秸应,其中包含許多用戶數(shù)據(jù):個人簡歷,朋友,照片和信息灸眼。 與任何社交網(wǎng)絡(luò)一樣卧檐,用戶隱私和互聯(lián)網(wǎng)安全至關(guān)重要。
您的目標是讓用戶控制他們與其他用戶共享的信息焰宣。 另外霉囚,您還可以讓他們根據(jù)他們與給定用戶的關(guān)系來調(diào)整隱私規(guī)則。
在使他們能夠控制并了解有關(guān)依賴注入(Dependency Injection)
及其如何幫助您的更多信息之前匕积,您需要確定問題盈罐。
1. Identifying the Issue
打開ProfileView.swift
并仔細查看ProfileView
的body
:
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: true) {
VStack {
// 1
ProfileHeaderView(
user: user,
canSendMessage: privacyLevel == .friend,
canStartVideoChat: privacyLevel == .friend
)
// 2
if privacyLevel == .friend {
UsersView(title: "Friends", users: user.friends)
PhotosView(photos: user.photos)
HistoryFeedView(posts: user.historyFeed)
} else {
// 3
RestrictedAccessView()
}
}
}.navigationTitle("Profile")
}
}
以下是代碼細分:
- 1) 您將
ProfileHeaderView
添加到VStack
的頂部,并指定僅當用戶是朋友時闪唆,消息和視頻通話選項才可用盅粪。 - 2) 如果用戶是朋友,則顯示朋友列表悄蕾,照片和帖子票顾。
- 3) 否則,您將顯示
RestrictedAccessView
帆调。
ProfileView
頂部的privacyLevel
值定義查看您的個人資料的用戶的訪問級別奠骄。 將privacyLevel
更改為.everyone
并運行該應(yīng)用程序以查看您的個人資料,就好像您不在好友列表中一樣:
已經(jīng)有基本的隱私控制措施番刊。 但是含鳞,用戶無法選擇誰可以看到其個人資料的哪些部分。 僅有兩個隱私級別是不夠的芹务。
當前蝉绷,ProfileView
根據(jù)隱私級別決定顯示哪些視圖。 由于以下幾個原因枣抱,這不是一個合適的解決方案:
- 它不是很容易測試熔吗。 盡管可以用UI測試進行覆蓋,但運行起來要比單元測試或集成測試昂貴沃但。
- 每次您決定擴展或修改應(yīng)用程序的功能時磁滚,
ProfileView
都需要進行大量調(diào)整。 它與PrivacyLevel
緊密結(jié)合宵晚,并承擔了比所需更多的責任垂攘。 - 隨著應(yīng)用程序的復(fù)雜性和功能的增長,維護此代碼將變得更加困難淤刃。
但是晒他,您可以改善這種情況并使用Dependency Injection
無縫添加新功能。
What Are Inversion of Control and Dependency Injection?
Inversion of Control是一種模式逸贾,可讓您反轉(zhuǎn)控制流程陨仅。為此津滞,您將一個類的所有職責(主要職責除外)移到了外部,使其成為從屬對象(dependencies)
灼伤。通過抽象触徐,您可以輕松地使依賴項互換。
您的類(DI
客戶對象)不知道其依賴項(即DI
服務(wù)對象)的實現(xiàn)狐赡。它還不知道如何創(chuàng)建它們撞鹉。通過消除類之間的緊密耦合關(guān)系,這使得代碼可測試和可維護颖侄。
Dependency Injection是幫助應(yīng)用Inversion of Control原理的幾種模式之一鸟雏。您可以通過多種方式實現(xiàn)依賴項注入(Dependency Injection)
,包括Constructor Injection, Setter Injection and Interface Injection览祖。
一種常見的方法稱為構(gòu)造函數(shù)注入(Constructor Injection)
孝鹊。這是您要看的第一個。
1. Constructor Injection
在Constructor Injection
或Initializer Injection
中展蒂,您將所有類依賴項作為構(gòu)造函數(shù)參數(shù)傳遞又活。更容易理解代碼的作用,因為您可以在一處立即看到類需要的所有依賴項锰悼。例如皇钞,查看以下代碼片段:
protocol EngineProtocol {
func start()
func stop()
}
protocol TransmissionProtocol {
func changeGear(gear: Gear)
}
final class Car {
private let engine: EngineProtocol
private let transmission: TransmissionProtocol
init(engine: EngineProtocol, transmission: TransmissionProtocol) {
self.engine = engine
self.transmission = transmission
}
}
在此代碼段中,EngineProtocol
和TransmissionProtocol
是服務(wù)松捉,而Car
是客戶端。 由于您劃分了職責并使用了抽象馆里,因此可以創(chuàng)建一個Car
實例隘世,該實例具有符合預(yù)期協(xié)議的任何依賴關(guān)系。 您甚至可以通過EngineProtocol
和TransmissionProtocol
的測試實現(xiàn)鸠踪,以對Car
進行一些單元測試丙者。
接下來,您將看到Setter Injection
营密。
2. Setter Injection
Setter Injection
或Method Injection
明顯不同械媒。 如本例所示,它需要依賴項setter
方法:
final class Car {
private var engine: EngineProtocol?
private var transmission: TransmissionProtocol?
func setEngine(engine: EngineProtocol) {
self.engine = engine
}
func setTransmission(transmission: TransmissionProtocol) {
self.transmission = transmission
}
}
當您只有幾個依賴項并且一些是可選的時评汰,這是一個好方法纷捞。 但是,很容易忘記設(shè)置必要的依賴項被去,因為沒有什么可以強迫您這樣做主儡。
接下來,您將探索Interface Injection
惨缆。
3. Interface Injection
Interface Injection
要求客戶端遵守用于inject dependencies
的協(xié)議糜值。 看這個例子:
protocol EngineMountable {
func mountEngine(engine: EngineProtocol)
}
protocol TransmissionMountable {
func mountTransmission(transmission: TransmissionProtocol)
}
final class Car: EngineMountable, TransmissionMountable {
private var engine: EngineProtocol?
private var transmission: TransmissionProtocol?
func mountEngine(engine: EngineProtocol) {
self.engine = engine
}
func mountTransmission(transmission: TransmissionProtocol) {
self.transmission = transmission
}
}
您的代碼更加分離丰捷。 此外,injector
可能完全不了解客戶的實際執(zhí)行情況寂汇。
Dependency Injection Container或DI Container是另一個重要的Dependency Injection
概念病往。 DI Container
負責注冊registering
和解決resolving
項目中的所有依賴項。 根據(jù)DI容器的復(fù)雜程度骄瓣,它可以處理依賴項的生命周期停巷,并在必要時自行自動注入依賴項。
在下一部分中累贤,您將創(chuàng)建一個基本的DI Container
叠穆。
Using Dependency Injection
最后,是時候運用您對模式的知識了臼膏! 使用以下命令創(chuàng)建一個名為ProfileContentProvider
的新Swift
文件:
import SwiftUI
protocol ProfileContentProviderProtocol {
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é)議硼被,但實現(xiàn)方式?jīng)Q定了要提供哪種內(nèi)容。
接下來渗磅,在您添加的協(xié)議下方添加以下類:
final class ProfileContentProvider: ProfileContentProviderProtocol {
let privacyLevel: PrivacyLevel
private let user: User
init(privacyLevel: PrivacyLevel, user: User) {
self.privacyLevel = privacyLevel
self.user = user
}
var canSendMessage: Bool {
privacyLevel > .everyone
}
var canStartVideoChat: Bool {
privacyLevel > .everyone
}
var photosView: AnyView {
privacyLevel > .everyone ?
AnyView(PhotosView(photos: user.photos)) :
AnyView(EmptyView())
}
var feedView: AnyView {
privacyLevel > .everyone ?
AnyView(HistoryFeedView(posts: user.historyFeed)) :
AnyView(RestrictedAccessView())
}
var friendsView: AnyView {
privacyLevel > .everyone ?
AnyView(UsersView(title: "Friends", users: user.friends)) :
AnyView(EmptyView())
}
}
現(xiàn)在嚷硫,您有一個單獨的提供者,其職責是:根據(jù)隱私級別決定如何顯示用戶個人資料始鱼。
接下來仔掸,切換到ProfileView.swift
并在ProfileView
的body
屬性上方添加以下代碼:
private let provider: ProfileContentProviderProtocol
init(provider: ProfileContentProviderProtocol, user: User) {
self.provider = provider
self.user = user
}
您在其初始值中設(shè)置ProfileView
的user
變量,因此刪除Mock.user()
值分配医清。
現(xiàn)在起暮,如下更新ProfileView
的body
屬性:
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")
}
}
有了這些更改,ProfileView
不再依賴privacyLevel
變量会烙,因為它通過其初始化程序構(gòu)造函數(shù)注入獲得了必要的依賴關(guān)系负懦。 從ProfileView
中刪除privacyLevel
常數(shù)。
注意:您會看到Xcode警告它在
ProfileView_Previews
中缺少參數(shù)柏腻。 不用擔心 您很快就會解決纸厉。
在這里,您可以開始看到這種方法的美五嫂。 現(xiàn)在颗品,該視圖完全不了解profile content
背后的業(yè)務(wù)邏輯。 您可以提供ProfileContentProviderProtocol
的任何實現(xiàn)沃缘,包括新的隱私級別躯枢,甚至可以模擬提供程序而無需更改任何代碼!
稍后您將對此進行驗證槐臀。 首先闺金,是時候設(shè)置Dependency Injection Container
,以幫助將所有DI infrastructure
收集到一個地方峰档。
1. Using a Dependency Injection Container
現(xiàn)在败匹,創(chuàng)建一個名為DIContainer.swift
的新文件寨昙,并添加以下內(nèi)容:
protocol DIContainerProtocol {
func register<Component>(type: Component.Type, component: Any)
func resolve<Component>(type: Component.Type) -> Component?
}
final class DIContainer: DIContainerProtocol {
// 1
static let shared = DIContainer()
// 2
private init() {}
// 3
var components: [String: Any] = [:]
func register<Component>(type: Component.Type, component: Any) {
// 4
components["\(type)"] = component
}
func resolve<Component>(type: Component.Type) -> Component? {
// 5
return components["\(type)"] as? Component
}
}
以下是分步說明:
- 1) 首先,創(chuàng)建一個類型為
DIContainer
的靜態(tài)屬性掀亩。 - 2) 由于您將初始化程序標記為私有舔哪,因此實質(zhì)上可以確保您的容器是單例的。 這樣可以防止意外使用多個實例和意外行為槽棍,例如丟失某些依賴項捉蚤。
- 3) 然后,您創(chuàng)建一個詞典以保留所有服務(wù)炼七。
- 4) 組件類型的字符串表示形式是字典中的鍵缆巧。
- 5) 您可以再次使用該類型來解決必要的依賴關(guān)系。
注意:本質(zhì)上豌拙,
DI Container
與其他任何模式一樣陕悬,是解決編程問題的一種方法。 您可以通過多種方式來實現(xiàn)它按傅,包括第三方框架捉超。
接下來,要使您的容器處理依賴關(guān)系唯绍,請打開ProfileView.swift
并更新ProfileView
的初始化程序拼岳,如下所示:
init(
provider: ProfileContentProviderProtocol =
DIContainer.shared.resolve(type: ProfileContentProviderProtocol.self)!,
user: User = DIContainer.shared.resolve(type: User.self)!
) {
self.provider = provider
self.user = user
}
現(xiàn)在,您的DIContainer
默認提供了必要的參數(shù)况芒。 但是惜纸,您始終可以自行傳遞依賴項以進行測試,也可以在容器中注冊模擬的依賴項绝骚。
接下來堪簿,在ProfileView
下面找到ProfileView_Previews
并進行更新:
struct ProfileView_Previews: PreviewProvider {
private static let user = Mock.user()
static var previews: some View {
ProfileView(
provider: ProfileContentProvider(privacyLevel: .friend, user: user),
user: user)
}
}
打開ProfileContentProvider.swift
。 使用相同的方法更新ProfileContentProvider
的初始化程序:
init(
privacyLevel: PrivacyLevel =
DIContainer.shared.resolve(type: PrivacyLevel.self)!,
user: User = DIContainer.shared.resolve(type: User.self)!
) {
self.privacyLevel = privacyLevel
self.user = user
}
最后皮壁,您必須定義依賴項的初始狀態(tài)以復(fù)制應(yīng)用程序的行為,然后再開始對其進行操作哪审。
在SceneDelegate.swift
中蛾魄,在profileView
的初始化上方添加以下代碼:
let container = DIContainer.shared
container.register(type: PrivacyLevel.self, component: PrivacyLevel.friend)
container.register(type: User.self, component: Mock.user())
container.register(
type: ProfileContentProviderProtocol.self,
component: ProfileContentProvider())
構(gòu)建并運行。 雖然該應(yīng)用看起來與以前完全一樣湿滓,但您知道它的內(nèi)部更漂亮滴须。
接下來,您將實現(xiàn)新功能叽奥。
Extending the Functionality
有時扔水,用戶希望向其好友列表中的人隱藏某些內(nèi)容或功能。 也許他們張貼聚會中的圖片朝氓,而他們只希望親密的朋友看到魔市。 或者主届,也許他們只想接收親密朋友的視頻通話。
無論出于何種原因待德,賦予密友額外訪問權(quán)限的能力都是一個很大的功能君丁。
要實現(xiàn)它,請轉(zhuǎn)到PrivacyLevel.swift
并添加另一種case
:
enum PrivacyLevel: Comparable {
case everyone, friend, closeFriend
}
接下來将宪,更新將處理新隱私級別的provider
绘闷。 轉(zhuǎn)到ProfileContentProvider.swift
并更新以下屬性:
var canStartVideoChat: Bool {
privacyLevel > .friend
}
var photosView: AnyView {
privacyLevel > .friend ?
AnyView(PhotosView(photos: user.photos)) :
AnyView(EmptyView())
}
使用此代碼,您可以確保只有密友可以訪問照片并發(fā)起視頻通話较坛。 您無需進行任何其他更改即可添加其他隱私級別印蔗。 您可以根據(jù)需要創(chuàng)建任意數(shù)量的隱私級別或組,為ProfileView
提供provider
丑勤,然后其他所有事務(wù)都將由您處理华嘹。
現(xiàn)在,構(gòu)建并運行:
如您所見确封,.friend
隱私級別的視頻通話圖標和“最近的照片”部分現(xiàn)在消失了除呵。 您實現(xiàn)了目標!
Adding User Preferences
是否想嘗試更復(fù)雜的用例爪喘? 如果您需要讓provider
根據(jù)用戶的隱私首選項做出決定颜曾,該怎么辦?
為解決此問題秉剑,您將添加一個新屏幕泛豪,用戶可以在其中確定誰可以訪問其個人資料的每個部分,使用UserDefaults
保存偏好設(shè)置侦鹏,并在每次更新偏好設(shè)置時重新加載個人資料屏幕诡曙。 您將使用Combine
框架來使其工作。
注意:如果您想更熟悉
Combine
或更新知識略水,請看一下Combine: Getting Started tutorial教程价卤。
首先,打開PrivacyLevel.swift
并將以下屬性和方法添加到PrivacyLevel
:
var title: String {
switch self {
case .everyone:
return "Everyone"
case .friend:
return "Friends only"
case .closeFriend:
return "Close friends only"
}
}
static func from(string: String) -> PrivacyLevel? {
switch string {
case everyone.title:
return everyone
case friend.title:
return friend
case closeFriend.title:
return closeFriend
default:
return nil
}
}
您將使用title
在要創(chuàng)建的新偏好設(shè)置屏幕上顯示隱私級別選項渊涝。 from(string :)
幫助從保存的UserDefaults
首選項重新創(chuàng)建PrivacyLevel
慎璧。
現(xiàn)在,在項目導(dǎo)航器中右鍵單擊Sociobox
文件夾跨释,然后選擇Add Files to “Sociobox”…
胸私。選擇PreferencesStore.swift
,然后單擊Add
鳖谈。打開文件并瀏覽代碼岁疼。
該類負責從UserDefaults
中保存和讀取用戶偏好設(shè)置。
您具有五個配置文件部分中的每一個的屬性缆娃,以及用于重置首選項的方法捷绒。 PreferencesStoreProtocol
符合ObservableObject
協(xié)議瑰排,使您的store
擁有一個publisher
,只要使用@Published
屬性標記的任何屬性發(fā)生更改疙驾,發(fā)布者都將發(fā)出該發(fā)布者凶伙。
進行任何更改時,任何SwiftUI
視圖甚至常規(guī)類都可以訂閱PreferencesStoreProtocol
并重新加載其內(nèi)容它碎。
接下來函荣,您將添加Preferences Screen
。
1. Adding the Preferences Screen
現(xiàn)在扳肛,右鍵單擊Views
文件夾傻挂,然后再次選擇Add Files to “Sociobox”…
,以添加UserPreferencesView.swift
挖息。打開它金拒,看看預(yù)覽:
這是新屏幕的外觀。
通過實現(xiàn)PreferencesStoreProtocol
套腹,使新屏幕保存用戶首選項绪抛。 將UserPreferencesView
的聲明更新為以下內(nèi)容:
struct UserPreferencesView<Store>: View where Store: PreferencesStoreProtocol {
像每種靜態(tài)類型的編程語言一樣,在編譯時定義和檢查類型电禀。 這就是問題所在:您不知道Store
在運行時將具有的確切類型幢码,但不要驚慌! 您所知道的是尖飞,Store
將符合PreferencesStoreProtocol
症副。 因此,您告訴編譯器Store
將實現(xiàn)此協(xié)議政基。
編譯器需要知道要用于視圖的特定類型贞铣。 稍后,當您創(chuàng)建UserPreferencesView
實例時沮明,需要在尖括號中使用特定類型而不是協(xié)議辕坝,如下所示:
UserPreferencesView<PreferencesStore>()
這樣,可以在編譯時檢查類型荐健。 現(xiàn)在酱畅,將以下屬性和初始化程序添加到UserPreferencesView
中:
private var store: Store
init(store: Store = DIContainer.shared.resolve(type: Store.self)!) {
self.store = store
}
使用上面的代碼,您可以讓UserPreferencesView
接收所需的依賴關(guān)系摧扇,而不是自己創(chuàng)建它。
更新body
屬性以使用store
訪問用戶首選項:
var body: some View {
NavigationView {
VStack {
PreferenceView(title: .photos, value: store.photosPreference) { value in
store.photosPreference = value
}
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")
}
以下是代碼細分:
- 1) 垂直堆棧
(vertical stack)
中的每個PreferenceView
都代表一個不同的配置文件部分挚歧,并帶有一個下拉菜單來選擇一個隱私級別扛稽。 - 2) 從
store
中讀取每個首選項的當前值。 - 3) 當用戶選擇隱私選項時滑负,將新值保存到
store
在张。
更新UserPreferencesView_Previews
的Previews
屬性用含,以便您可以再次看到預(yù)覽:
static var previews: some View {
UserPreferencesView(store: PreferencesStore())
}
在SceneDelegate.swift
中,將store dependency
注冊到您的容器中:
container.register(type: PreferencesStore.self, component: PreferencesStore())
2. Adding Combine
接下來帮匾,轉(zhuǎn)到ProfileContentProvider.swift
并在文件頂部導(dǎo)入Combine
:
import Combine
然后啄骇,像使用UserPreferencesView
一樣更新其聲明:
final class ProfileContentProvider<Store>: ProfileContentProviderProtocol
where Store: PreferencesStoreProtocol {
現(xiàn)在,更新ProfileContentProviderProtocol
的聲明:
protocol ProfileContentProviderProtocol: ObservableObject {
此代碼使ProfileView
可以訂閱ProfileContentProvider
中的更改瘟斜,并在用戶選擇新的首選項時立即更新狀態(tài)缸夹。
在ProfileContentProvider
中,為store
添加一個屬性并替換初始化程序:
private var store: Store
private var cancellables: Set<AnyCancellable> = []
init(
privacyLevel: PrivacyLevel =
DIContainer.shared.resolve(type: PrivacyLevel.self)!,
user: User = DIContainer.shared.resolve(type: User.self)!,
// 1
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)
}
這是您所做的:
- 1)
DI Container
提供PreferencesStore
的實例螺句。 - 2) 您使用
objectWillChange
屬性訂閱PreferencesStoreProtocol
的發(fā)布者虽惭。 - 3) 當
store
中的屬性發(fā)生更改時,也會使ProfileContentProviderProtocol
的發(fā)布者發(fā)出蛇尚。
現(xiàn)在芽唇,更新ProfileContentProvider
的屬性以使用store
的屬性,而不使用PrivacyLevel
枚舉的實例:
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(EmptyView())
}
var friendsView: AnyView {
privacyLevel >= store.friendsListPreference ?
AnyView(UsersView(title: "Friends", users: user.friends)) :
AnyView(EmptyView())
}
除不再直接使用enum
外取劫,其他所有內(nèi)容保持不變匆笤。
3. Bringing It All Together
要訂閱provider
中的更改,請打開ProfileView.swift
并同時更改ProfileView
的聲明:
struct ProfileView<ContentProvider>: View
where ContentProvider: ProfileContentProviderProtocol {
更新provider
屬性以使用通用屬性:
@ObservedObject private var provider: ContentProvider
在SwiftUI
視圖中使用@ObservedObject
時谱邪,您將訂閱其發(fā)布者炮捧。 該視圖在發(fā)出時會重新加載。
也更新初始化器:
init(
provider: ContentProvider =
DIContainer.shared.resolve(type: ContentProvider.self)!,
user: User = DIContainer.shared.resolve(type: User.self)!
) {
self.provider = provider
self.user = user
}
然后在body
屬性內(nèi)的navigationTitle(“ Profile”)
下方添加以下代碼:
.navigationBarItems(trailing: Button(action: {}) {
NavigationLink(destination: UserPreferencesView<PreferencesStore>()) {
Image(systemName: "gear")
}
})
您添加了一個導(dǎo)航欄按鈕虾标,該按鈕會將用戶帶到首選項屏幕寓盗。
現(xiàn)在返回SceneDelegate.swift
以更新依賴項注冊。 由于您的很多協(xié)議和類都是通用的璧函,因此將它們?nèi)恳黄鹗褂米兊糜悬c難以閱讀傀蚌。
為了簡化操作,請在scene(_:willConnectTo:options:)
上方為提供者(provider)
創(chuàng)建一個新的typealias
:
typealias Provider = ProfileContentProvider<PreferencesStore>
通過刪除以下內(nèi)容使用新的typealias
:
container.register(
type: ProfileContentProviderProtocol.self,
component: ProfileContentProvider())
現(xiàn)在蘸吓,添加以下_after_
調(diào)用以注冊PreferencesStore
:
container.register(type: Provider.self, component: Provider())
注意:您必須最后注冊
Provider
善炫,因為其初始化程序希望隱私級別,用戶和存儲區(qū)已經(jīng)存在于DI Container
中库继。
將<Provider>
添加到profileView
的初始化中:
let profileView = ProfileView<Provider>()
要獲得可用的預(yù)覽箩艺,請打開ProfileView.swift
并在ProfileView_Previews
中添加相同的設(shè)置:
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
typealias Provider = ProfileContentProvider<PreferencesStore>
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())
return ProfileView<Provider>()
}
}
經(jīng)過艱苦的工作,是時候看看它們?nèi)绾我黄鸸ぷ髁恕?運行應(yīng)用程序以查看結(jié)果:
在本教程中宪萄,您學(xué)習(xí)了Dependency Injection
模式以及如何在項目中構(gòu)建和應(yīng)用它艺谆。 根據(jù)您正在從事的項目,您可以考慮使用第三方解決方案拜英。
要了解更多信息静汤,請閱讀我們的Swinject教程。 即使您沒有使用第三方框架進行依賴項注入(dependency injection)
,您也會發(fā)現(xiàn)一些方便的測試示例虫给。
后記
本篇主要講述了基于SwiftUI簡單App的
Dependency Injection
應(yīng)用藤抡,感興趣的給個贊或者關(guān)注~~~