Fix Swift 6 concurrency warnings

- ContentView: Wrap Timer callbacks in Task { @MainActor in } to
  properly access main actor-isolated properties (updateAutocomplete,
  messages)
- BitchatApp: Capture nickname before entering background queue to
  avoid accessing main actor-isolated property from Sendable closure
This commit is contained in:
jack
2025-11-25 09:50:02 -10:00
parent b8a8b940b7
commit 76d8b3fa7f
2 changed files with 17 additions and 14 deletions
+2 -1
View File
@@ -53,9 +53,10 @@ struct BitchatApp: App {
// Inject live Noise service into VerificationService to avoid creating new BLE instances // Inject live Noise service into VerificationService to avoid creating new BLE instances
VerificationService.shared.configure(with: chatViewModel.meshService.getNoiseService()) VerificationService.shared.configure(with: chatViewModel.meshService.getNoiseService())
// Prewarm Nostr identity and QR to make first VERIFY sheet fast // Prewarm Nostr identity and QR to make first VERIFY sheet fast
let nickname = chatViewModel.nickname
DispatchQueue.global(qos: .utility).async { DispatchQueue.global(qos: .utility).async {
let npub = try? idBridge.getCurrentNostrIdentity()?.npub let npub = try? idBridge.getCurrentNostrIdentity()?.npub
_ = VerificationService.shared.buildMyQRString(nickname: chatViewModel.nickname, npub: npub) _ = VerificationService.shared.buildMyQRString(nickname: nickname, npub: npub)
} }
appDelegate.chatViewModel = chatViewModel appDelegate.chatViewModel = chatViewModel
+7 -5
View File
@@ -465,7 +465,8 @@ struct ContentView: View {
} else { } else {
// Schedule a delayed scroll // Schedule a delayed scroll
scrollThrottleTimer?.invalidate() scrollThrottleTimer?.invalidate()
scrollThrottleTimer = Timer.scheduledTimer(withTimeInterval: TransportConfig.uiScrollThrottleSeconds, repeats: false) { _ in scrollThrottleTimer = Timer.scheduledTimer(withTimeInterval: TransportConfig.uiScrollThrottleSeconds, repeats: false) { [weak viewModel] _ in
Task { @MainActor in
lastScrollTime = Date() lastScrollTime = Date()
let contextKey: String = { let contextKey: String = {
switch locationManager.selectedChannel { switch locationManager.selectedChannel {
@@ -474,8 +475,7 @@ struct ContentView: View {
} }
}() }()
let count = windowCountPublic let count = windowCountPublic
let target = viewModel.messages.suffix(count).last.map { "\(contextKey)|\($0.id)" } let target = viewModel?.messages.suffix(count).last.map { "\(contextKey)|\($0.id)" }
DispatchQueue.main.async {
if let target = target { proxy.scrollTo(target, anchor: .bottom) } if let target = target { proxy.scrollTo(target, anchor: .bottom) }
} }
} }
@@ -643,9 +643,11 @@ struct ContentView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.onChange(of: messageText) { newValue in .onChange(of: messageText) { newValue in
autocompleteDebounceTimer?.invalidate() autocompleteDebounceTimer?.invalidate()
autocompleteDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false) { _ in autocompleteDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false) { [weak viewModel] _ in
let cursorPosition = newValue.count let cursorPosition = newValue.count
viewModel.updateAutocomplete(for: newValue, cursorPosition: cursorPosition) Task { @MainActor in
viewModel?.updateAutocomplete(for: newValue, cursorPosition: cursorPosition)
}
} }
} }