Skip to content

Commit 7b50ab7

Browse files
committed
fix(concurrency): replace view model main-thread GCD hops
1 parent 31f39ff commit 7b50ab7

File tree

5 files changed

+75
-82
lines changed

5 files changed

+75
-82
lines changed

BDKSwiftExampleWallet/View Model/Activity/ActivityListViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ActivityListViewModel {
2626

2727
private var updateProgress: @Sendable (UInt64, UInt64) -> Void {
2828
{ [weak self] inspected, total in
29-
DispatchQueue.main.async {
29+
Task { @MainActor [weak self] in
3030
self?.totalScripts = total
3131
self?.inspectedScripts = inspected
3232
self?.progress = total > 0 ? Float(inspected) / Float(total) : 0

BDKSwiftExampleWallet/View Model/OnboardingViewModel.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class OnboardingViewModel: ObservableObject {
129129
func createWallet() {
130130
// Check if wallet already exists
131131
if let existingBackup = try? bdkClient.getBackupInfo() {
132-
DispatchQueue.main.async {
132+
Task { @MainActor in
133133
self.isOnboarding = false
134134
}
135135
return
@@ -139,7 +139,7 @@ class OnboardingViewModel: ObservableObject {
139139
return
140140
}
141141

142-
DispatchQueue.main.async {
142+
Task { @MainActor in
143143
self.isCreatingWallet = true
144144
}
145145

@@ -158,23 +158,23 @@ class OnboardingViewModel: ObservableObject {
158158
} else {
159159
try self.bdkClient.createWalletFromSeed(self.words)
160160
}
161-
DispatchQueue.main.async {
161+
await MainActor.run {
162162
self.isCreatingWallet = false
163163
self.isOnboarding = false
164164
NotificationCenter.default.post(name: .walletCreated, object: nil)
165165
}
166166
} catch let error as CreateWithPersistError {
167-
DispatchQueue.main.async {
167+
await MainActor.run {
168168
self.isCreatingWallet = false
169169
self.createWithPersistError = error
170170
}
171171
} catch let error as AppError {
172-
DispatchQueue.main.async {
172+
await MainActor.run {
173173
self.isCreatingWallet = false
174174
self.onboardingViewError = error
175175
}
176176
} catch {
177-
DispatchQueue.main.async {
177+
await MainActor.run {
178178
self.isCreatingWallet = false
179179
self.onboardingViewError = .generic(message: error.localizedDescription)
180180
}

BDKSwiftExampleWallet/View Model/Receive/ReceiveViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ extension ReceiveViewModel {
9797
return
9898
}
9999

100-
DispatchQueue.main.async {
100+
Task { @MainActor in
101101
self.receiveViewError = .generic(message: error.localizedDescription)
102102
self.showingReceiveViewErrorAlert = true
103103
}

BDKSwiftExampleWallet/View Model/Settings/SettingsViewModel.swift

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class SettingsViewModel: ObservableObject {
2424

2525
private var updateProgressFullScan: @Sendable (UInt64) -> Void {
2626
{ [weak self] inspected in
27-
DispatchQueue.main.async {
27+
Task { @MainActor [weak self] in
2828
self?.inspectedScripts = inspected
2929
}
3030
}
@@ -46,9 +46,7 @@ class SettingsViewModel: ObservableObject {
4646
}
4747

4848
func getAddressType() {
49-
DispatchQueue.main.async {
50-
self.addressType = self.bdkClient.getAddressType()
51-
}
49+
self.addressType = self.bdkClient.getAddressType()
5250
}
5351

5452
func delete() {
@@ -66,33 +64,23 @@ class SettingsViewModel: ObservableObject {
6664
do {
6765
let inspector = WalletFullScanScriptInspector(updateProgress: updateProgressFullScan)
6866
try await bdkClient.fullScanWithInspector(inspector)
69-
DispatchQueue.main.async {
70-
NotificationCenter.default.post(
71-
name: .transactionSent,
72-
object: nil
73-
)
74-
self.walletSyncState = .synced
75-
}
67+
NotificationCenter.default.post(
68+
name: .transactionSent,
69+
object: nil
70+
)
71+
self.walletSyncState = .synced
7672
} catch let error as CannotConnectError {
77-
DispatchQueue.main.async {
78-
self.settingsError = .generic(message: error.localizedDescription)
79-
self.showingSettingsViewErrorAlert = true
80-
}
73+
self.settingsError = .generic(message: error.localizedDescription)
74+
self.showingSettingsViewErrorAlert = true
8175
} catch let error as EsploraError {
82-
DispatchQueue.main.async {
83-
self.settingsError = .generic(message: error.localizedDescription)
84-
self.showingSettingsViewErrorAlert = true
85-
}
76+
self.settingsError = .generic(message: error.localizedDescription)
77+
self.showingSettingsViewErrorAlert = true
8678
} catch let error as PersistenceError {
87-
DispatchQueue.main.async {
88-
self.settingsError = .generic(message: error.localizedDescription)
89-
self.showingSettingsViewErrorAlert = true
90-
}
79+
self.settingsError = .generic(message: error.localizedDescription)
80+
self.showingSettingsViewErrorAlert = true
9181
} catch {
92-
DispatchQueue.main.async {
93-
self.walletSyncState = .error(error)
94-
self.showingSettingsViewErrorAlert = true
95-
}
82+
self.walletSyncState = .error(error)
83+
self.showingSettingsViewErrorAlert = true
9684
}
9785
}
9886

BDKSwiftExampleWallet/View Model/WalletViewModel.swift

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class WalletViewModel {
5050

5151
private var updateProgress: @Sendable (UInt64, UInt64) -> Void {
5252
{ [weak self] inspected, total in
53-
DispatchQueue.main.async {
53+
Task { @MainActor [weak self] in
5454
// When using Kyoto, progress is provided separately as percent
5555
if self?.isKyotoClient == true { return }
5656
self?.totalScripts = total
@@ -63,7 +63,7 @@ class WalletViewModel {
6363

6464
private var updateKyotoProgress: @Sendable (Float) -> Void {
6565
{ [weak self] rawProgress in
66-
DispatchQueue.main.async { [weak self] in
66+
Task { @MainActor [weak self] in
6767
guard let self else { return }
6868
let sanitized = rawProgress.isFinite ? min(max(rawProgress, 0), 100) : 0
6969
self.progress = sanitized
@@ -75,7 +75,7 @@ class WalletViewModel {
7575

7676
private var updateProgressFullScan: @Sendable (UInt64) -> Void {
7777
{ [weak self] inspected in
78-
DispatchQueue.main.async {
78+
Task { @MainActor [weak self] in
7979
self?.inspectedScripts = inspected
8080
}
8181
}
@@ -99,25 +99,27 @@ class WalletViewModel {
9999
object: nil,
100100
queue: .main
101101
) { [weak self] notification in
102-
guard let self else { return }
103-
// Ignore Kyoto updates unless client type is Kyoto
104-
if self.bdkClient.getClientType() != .kyoto { return }
105-
if let progress = notification.userInfo?["progress"] as? Float {
106-
self.updateKyotoProgress(progress)
107-
if let height = notification.userInfo?["height"] as? Int {
108-
self.currentBlockHeight = UInt32(max(height, 0))
109-
}
110-
// Consider any progress update as evidence of an active connection
111-
// so the UI does not falsely show a red disconnected indicator while syncing.
112-
if progress > 0 {
113-
self.isKyotoConnected = true
114-
}
102+
Task { @MainActor [weak self] in
103+
guard let self else { return }
104+
// Ignore Kyoto updates unless client type is Kyoto
105+
if self.bdkClient.getClientType() != .kyoto { return }
106+
if let progress = notification.userInfo?["progress"] as? Float {
107+
self.updateKyotoProgress(progress)
108+
if let height = notification.userInfo?["height"] as? Int {
109+
self.currentBlockHeight = UInt32(max(height, 0))
110+
}
111+
// Consider any progress update as evidence of an active connection
112+
// so the UI does not falsely show a red disconnected indicator while syncing.
113+
if progress > 0 {
114+
self.isKyotoConnected = true
115+
}
115116

116-
// Update sync state based on Kyoto progress
117-
if progress >= 100 {
118-
self.walletSyncState = .synced
119-
} else if progress > 0 {
120-
self.walletSyncState = .syncing
117+
// Update sync state based on Kyoto progress
118+
if progress >= 100 {
119+
self.walletSyncState = .synced
120+
} else if progress > 0 {
121+
self.walletSyncState = .syncing
122+
}
121123
}
122124
}
123125
}
@@ -127,16 +129,19 @@ class WalletViewModel {
127129
object: nil,
128130
queue: .main
129131
) { [weak self] notification in
130-
if let connected = notification.userInfo?["connected"] as? Bool {
131-
self?.isKyotoConnected = connected
132+
Task { @MainActor [weak self] in
133+
guard let self else { return }
134+
if let connected = notification.userInfo?["connected"] as? Bool {
135+
self.isKyotoConnected = connected
132136

133-
// When Kyoto connects, update sync state if needed
134-
if connected && self?.walletSyncState == .notStarted {
135-
// Check current progress to determine state
136-
if let progress = self?.progress, progress >= 100 {
137-
self?.walletSyncState = .synced
138-
} else {
139-
self?.walletSyncState = .syncing
137+
// When Kyoto connects, update sync state if needed
138+
if connected && self.walletSyncState == .notStarted {
139+
// Check current progress to determine state
140+
if self.progress >= 100 {
141+
self.walletSyncState = .synced
142+
} else {
143+
self.walletSyncState = .syncing
144+
}
140145
}
141146
}
142147
}
@@ -147,19 +152,19 @@ class WalletViewModel {
147152
object: nil,
148153
queue: .main
149154
) { [weak self] notification in
150-
guard let self else { return }
151-
// Ignore Kyoto updates unless client type is Kyoto
152-
if self.bdkClient.getClientType() != .kyoto { return }
153-
if let height = notification.userInfo?["height"] as? Int {
154-
self.currentBlockHeight = UInt32(max(height, 0))
155-
// Receiving chain height implies we have peer connectivity
156-
self.isKyotoConnected = true
157-
// Ensure UI reflects syncing as soon as we see chain activity
158-
if self.walletSyncState == .notStarted { self.walletSyncState = .syncing }
159-
// Auto-refresh wallet data when Kyoto receives new blocks
160-
self.getBalance()
161-
self.getTransactions()
162-
Task {
155+
Task { @MainActor [weak self] in
156+
guard let self else { return }
157+
// Ignore Kyoto updates unless client type is Kyoto
158+
if self.bdkClient.getClientType() != .kyoto { return }
159+
if let height = notification.userInfo?["height"] as? Int {
160+
self.currentBlockHeight = UInt32(max(height, 0))
161+
// Receiving chain height implies we have peer connectivity
162+
self.isKyotoConnected = true
163+
// Ensure UI reflects syncing as soon as we see chain activity
164+
if self.walletSyncState == .notStarted { self.walletSyncState = .syncing }
165+
// Auto-refresh wallet data when Kyoto receives new blocks
166+
self.getBalance()
167+
self.getTransactions()
163168
await self.getPrices()
164169
}
165170
}
@@ -170,10 +175,10 @@ class WalletViewModel {
170175
object: nil,
171176
queue: .main
172177
) { [weak self] _ in
173-
guard let self else { return }
174-
self.getBalance()
175-
self.getTransactions()
176-
Task {
178+
Task { @MainActor [weak self] in
179+
guard let self else { return }
180+
self.getBalance()
181+
self.getTransactions()
177182
await self.getPrices()
178183
}
179184
}

0 commit comments

Comments
 (0)