Swift 6 Migration (#5733)

* Enable Swift 6.2 with default main actor isolation for the app target

Turn on SWIFT_VERSION 6.2, SWIFT_APPROACHABLE_CONCURRENCY and SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor for the ElementX target, matching the NSE and Share Extension (which already use 6.2 without the main actor default).

# Conflicts:
#	ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

* Update the code generation templates for Swift 6

Emit nonisolated/@unchecked Sendable mocks and nonisolated string enums, and mark async mock methods @concurrent so they keep suspending like the real implementations (otherwise intermediate states are unobservable in tests). Includes the regenerated mocks and strings.

# Conflicts:
#	ElementX/Sources/Mocks/Generated/GeneratedMocks.swift

* Make the data layer and services nonisolated and Sendable

Annotate the value-type models, Rust SDK proxy wrappers, timeline item graph, settings, logging and NSE-shared types as nonisolated, and conform the service protocols to Sendable so their existentials can cross isolation boundaries. These types run off the main actor and no longer inherit the target's default main actor isolation.

* Adopt the new isolation across call sites, mocks and signatures

Update call sites, closure signatures and mock configuration helpers to match the nonisolated/Sendable data layer, including the main actor isolated mock inits and the @Sendable filter on recentlyVisitedRooms.

^ Conflicts:
^	ElementX/Sources/Services/SecureBackup/SecureBackupController.swift

* Remove redundant @MainActor annotations

With the app target defaulting to main actor isolation, the explicit @MainActor on view models, coordinators, views and their protocols is no longer required.

* Fix concurrency hotspots surfaced by the migration

Resolve the room summary main-thread deadlock by moving diff processing onto AsyncStreams, add the SDKListener.onMainActor FIFO bridge for SDK callbacks, hop KVO/timer/UIKit callbacks to the main actor via assumeIsolated, add isolated deinits, and apply the few justified nonisolated(unsafe) escapes for queue-confined Apple APIs.

* Fix the unit tests for Swift 6 concurrency

Hop the waitForConfirmation body onto the caller's actor (Swift Testing runs confirmation closures off-actor), and adjust the affected suites and async signatures.

* offload diffs on concurrent threads instead of main actor

* make room info protocols sendable

* use a task group for fetching data so that everything runs concurrently in the TextRoomTimelineView when getting the preview.

* use the generic onMainActor implementation, which already makes a stream and send the continuation on the main actor.

* small pr comments issues being addressed

* update enterprise

* Update StaticCode to 1.1.0 and specify the required Swift isolation.

* fix ElementCallServiceTests

* update enterprise

* re-generate mocks

* update enterprise

* enterprise new code conformed to swift 6

---------

Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
Mauro
2026-06-18 17:55:31 +02:00
committed by GitHub
parent 7edca8b5a1
commit a520d94760
444 changed files with 3677 additions and 3361 deletions
@@ -14,7 +14,6 @@ import AuthenticationServices
/// meaning that the user doesn't need to sign in again. `SFSafariViewController` doesn't
/// have access to this session, and for some reason `prefersEphemeralWebBrowserSession`
/// isn't sharing the session back to Safari.
@MainActor
class OAuthAccountSettingsPresenter: NSObject {
private let accountURL: URL
private let redirectURL: URL
@@ -8,7 +8,6 @@
import Combine
@MainActor
protocol AdvancedSettingsScreenViewModelProtocol {
var context: AdvancedSettingsScreenViewModelType.Context { get }
}
@@ -8,7 +8,6 @@
import Combine
@MainActor
protocol AnalyticsSettingsScreenViewModelProtocol {
var context: AnalyticsSettingsScreenViewModelType.Context { get }
}
@@ -8,7 +8,6 @@
import Combine
@MainActor
protocol DeactivateAccountScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<DeactivateAccountScreenViewModelAction, Never> { get }
var context: DeactivateAccountScreenViewModelType.Context { get }
@@ -59,7 +59,7 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
case .clearCache:
actionsSubject.send(.clearCache)
case .markAllRoomsAsRead:
Task.detached {
Task {
await self.clientProxy?.markAllRoomsAsRead()
}
}
@@ -8,7 +8,6 @@
import Combine
@MainActor
protocol DeveloperOptionsScreenViewModelProtocol {
var actions: AnyPublisher<DeveloperOptionsScreenViewModelAction, Never> { get }
var context: DeveloperOptionsScreenViewModelType.Context { get }
@@ -9,7 +9,6 @@
import Combine
// periphery:ignore - markdown protocol
@MainActor
protocol LegalInformationScreenViewModelProtocol {
var context: LegalInformationScreenViewModelType.Context { get }
}
@@ -8,7 +8,6 @@
import Combine
@MainActor
protocol NotificationSettingsEditScreenViewModelProtocol {
var actions: AnyPublisher<NotificationSettingsEditScreenViewModelAction, Never> { get }
var context: NotificationSettingsEditScreenViewModelType.Context { get }
@@ -28,7 +28,7 @@ struct NotificationSettingsEditScreenRoomCell: View {
.accessibilityIdentifier(A11yIdentifiers.notificationSettingsEditScreen.roomName(room.name))
}
@ViewBuilder @MainActor
@ViewBuilder
var avatar: some View {
if dynamicTypeSize < .accessibility3 {
RoomAvatarImage(avatar: room.avatar,
@@ -39,7 +39,6 @@ struct NotificationSettingsEditScreenRoomCell: View {
}
}
@MainActor
var roomDetailsLabel: ListRowDetails<EmptyView>? {
guard let mode = room.notificationMode else { return nil }
return .label(title: context.viewState.strings.string(for: mode),
@@ -8,7 +8,6 @@
import Combine
@MainActor
protocol NotificationSettingsScreenViewModelProtocol {
var actions: AnyPublisher<NotificationSettingsScreenViewModelAction, Never> { get }
var context: NotificationSettingsScreenViewModelType.Context { get }
@@ -8,7 +8,6 @@
import Combine
@MainActor
protocol SettingsScreenViewModelProtocol {
var actions: AnyPublisher<SettingsScreenViewModelAction, Never> { get }
var context: SettingsScreenViewModelType.Context { get }
@@ -134,17 +134,13 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe
try await withThrowingTaskGroup(of: Void.self) { group in
if state.avatarDidChange {
group.addTask {
if let localMedia = await self.state.localMedia {
try await self.clientProxy.setUserAvatar(media: localMedia).get()
} else if await self.state.selectedAvatarURL == nil {
try await self.clientProxy.removeUserAvatar().get()
}
try await self.saveAvatar()
}
}
if state.nameDidChange {
group.addTask {
try await self.clientProxy.setUserDisplayName(self.state.bindings.name).get()
try await self.saveDisplayName()
}
}
@@ -158,4 +154,16 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe
message: L10n.screenEditProfileError)
}
}
private func saveAvatar() async throws {
if let localMedia = state.localMedia {
try await clientProxy.setUserAvatar(media: localMedia).get()
} else if state.selectedAvatarURL == nil {
try await clientProxy.removeUserAvatar().get()
}
}
private func saveDisplayName() async throws {
try await clientProxy.setUserDisplayName(state.bindings.name).get()
}
}
@@ -9,7 +9,6 @@
import Combine
import Foundation
@MainActor
protocol UserDetailsEditScreenViewModelProtocol {
var actions: AnyPublisher<UserDetailsEditScreenViewModelAction, Never> { get }
var context: UserDetailsEditScreenViewModelType.Context { get }