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
@@ -9,7 +9,7 @@
import Foundation
/// Represents all reactions of the same type for a single event.
struct AggregatedReaction: Hashable, Identifiable {
nonisolated struct AggregatedReaction: Hashable, Identifiable {
/// Length at which we ellipsize a reaction key for display
/// Reactions can be free text, so we need to limit the length
/// displayed on screen.
@@ -28,14 +28,14 @@ struct AggregatedReaction: Hashable, Identifiable {
}
/// Details of who sent the reaction
struct ReactionSender: Hashable, Identifiable {
nonisolated struct ReactionSender: Hashable, Identifiable {
/// The id of the user who sent the reaction
let id: String
/// The time that the reaction was received on the original homeserver
let timestamp: Date
}
extension AggregatedReaction {
nonisolated extension AggregatedReaction {
/// The number of times this reactions was sent.
var count: Int {
senders.count
@@ -13,7 +13,7 @@ import SwiftUI
/// Represents an issue with a timeline item's authenticity such as coming from an
/// unsigned session or being sent unencrypted in an encrypted room. See Rust's
/// `ShieldStateCode` for more information about the meaning of the cases.
enum EncryptionAuthenticity: Hashable {
nonisolated enum EncryptionAuthenticity: Hashable {
enum Color { case red, gray }
case notGuaranteed(color: Color)
@@ -65,7 +65,7 @@ enum EncryptionAuthenticity: Hashable {
}
}
extension EncryptionAuthenticity {
nonisolated extension EncryptionAuthenticity {
init?(shieldState: ShieldState) {
switch shieldState {
case .red(let code):
@@ -8,12 +8,12 @@
import Foundation
struct ReadReceipt: Hashable {
nonisolated struct ReadReceipt: Hashable {
let userID: String
let formattedTimestamp: String?
}
extension ReadReceipt: Identifiable {
nonisolated extension ReadReceipt: Identifiable {
var id: String {
userID
}
@@ -9,7 +9,7 @@
import Foundation
/// Properties of a matrix event that are common between all timeline items.
struct RoomTimelineItemProperties: Hashable {
nonisolated struct RoomTimelineItemProperties: Hashable {
/// Information on the item this one replying to
var replyDetails: TimelineItemReplyDetails?
/// Whether it's part of a thread or not
@@ -8,7 +8,7 @@
import Foundation
enum TimelineEventContent: Hashable {
nonisolated enum TimelineEventContent: Hashable {
case message(EventBasedMessageTimelineItemContentType)
case poll(question: String)
case liveLocation
@@ -8,7 +8,7 @@
import MatrixRustSDK
import SwiftUI
struct TimelineItemKeyForwarder: Identifiable, Hashable {
nonisolated struct TimelineItemKeyForwarder: Identifiable, Hashable {
let id: String
let displayName: String?
@@ -8,12 +8,12 @@
import Foundation
struct TimelineItemReply {
nonisolated struct TimelineItemReply {
let details: TimelineItemReplyDetails
let isThreaded: Bool
}
enum TimelineItemReplyDetails: Hashable {
nonisolated enum TimelineItemReplyDetails: Hashable {
case notLoaded(eventID: String)
case loading(eventID: String)
case loaded(sender: TimelineItemSender, eventID: String, eventContent: TimelineEventContent)
@@ -6,7 +6,7 @@
// Please see LICENSE files in the repository root for full details.
//
enum TimelineItemThreadSummary: Hashable {
nonisolated enum TimelineItemThreadSummary: Hashable {
case notLoaded
case loading
case loaded(senderID: String, sender: TimelineItemSender, latestEventContent: TimelineEventContent, numberOfReplies: Int)