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,4 +9,4 @@
import Foundation
// periphery: ignore - markdown protocol
protocol DecorationTimelineItemProtocol: RoomTimelineItemProtocol { }
nonisolated protocol DecorationTimelineItemProtocol: RoomTimelineItemProtocol { }
@@ -8,7 +8,7 @@
import Foundation
enum EventBasedMessageTimelineItemContentType: Hashable {
nonisolated enum EventBasedMessageTimelineItemContentType: Hashable {
case audio(AudioRoomTimelineItemContent)
case emote(EmoteRoomTimelineItemContent)
case file(FileRoomTimelineItemContent)
@@ -20,11 +20,11 @@ enum EventBasedMessageTimelineItemContentType: Hashable {
case voice(AudioRoomTimelineItemContent)
}
protocol EventBasedMessageTimelineItemProtocol: EventBasedTimelineItemProtocol {
nonisolated protocol EventBasedMessageTimelineItemProtocol: EventBasedTimelineItemProtocol {
var contentType: EventBasedMessageTimelineItemContentType { get }
}
extension EventBasedMessageTimelineItemProtocol {
nonisolated extension EventBasedMessageTimelineItemProtocol {
var supportsMediaCaption: Bool {
switch contentType {
case .audio, .file, .image, .video:
@@ -9,7 +9,7 @@
import Foundation
import UIKit
protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringConvertible {
nonisolated protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringConvertible {
var timestamp: Date { get }
var isOutgoing: Bool { get }
var isEditable: Bool { get }
@@ -22,7 +22,7 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringC
var properties: RoomTimelineItemProperties { get }
}
extension EventBasedTimelineItemProtocol {
nonisolated extension EventBasedTimelineItemProtocol {
var description: String {
"\(String(describing: Self.self)): id: \(id), timestamp: \(timestamp), isOutgoing: \(isOutgoing), properties: \(properties)"
}
@@ -8,7 +8,7 @@
import Foundation
struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
nonisolated struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -9,7 +9,7 @@
import UIKit
import UniformTypeIdentifiers
struct AudioRoomTimelineItemContent: Hashable {
nonisolated struct AudioRoomTimelineItemContent: Hashable {
let filename: String
var caption: String?
var formattedCaption: AttributedString?
@@ -8,7 +8,7 @@
import UIKit
struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
nonisolated struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -8,7 +8,7 @@
import UIKit
struct EmoteRoomTimelineItemContent: Hashable {
nonisolated struct EmoteRoomTimelineItemContent: Hashable {
let body: String
var formattedBody: AttributedString?
/// The original textual representation of the formatted body directly from the event (usually HTML code)
@@ -9,7 +9,7 @@
import UIKit
import UniformTypeIdentifiers
struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
nonisolated struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -9,7 +9,7 @@
import Foundation
import UniformTypeIdentifiers
struct FileRoomTimelineItemContent: Hashable {
nonisolated struct FileRoomTimelineItemContent: Hashable {
let filename: String
var caption: String?
var formattedCaption: AttributedString?
@@ -9,7 +9,7 @@
import UIKit
import UniformTypeIdentifiers
struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
nonisolated struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -9,7 +9,7 @@
import Foundation
import UniformTypeIdentifiers
struct ImageRoomTimelineItemContent: Hashable {
nonisolated struct ImageRoomTimelineItemContent: Hashable {
let filename: String
var caption: String?
var formattedCaption: AttributedString?
@@ -8,7 +8,7 @@
import Foundation
import MatrixRustSDK
struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
nonisolated struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
/// Always empty just here for protocol conformance
var body: String {
@@ -25,14 +25,14 @@ struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
var properties = RoomTimelineItemProperties()
}
struct LiveLocationRoomTimelineItemContent: Equatable {
nonisolated struct LiveLocationRoomTimelineItemContent: Equatable {
let isLive: Bool
let timeoutDate: Date
let lastGeoURI: GeoURI?
}
extension LiveLocationRoomTimelineItemContent {
nonisolated extension LiveLocationRoomTimelineItemContent {
init(from content: MatrixRustSDK.LiveLocationContent, timestamp: Date) {
isLive = content.isLive
timeoutDate = timestamp.addingTimeInterval(Double(content.timeoutMs) / 1000)
@@ -8,7 +8,7 @@
import Foundation
struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
nonisolated struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -8,7 +8,7 @@
import MatrixRustSDK
struct LocationRoomTimelineItemContent: Hashable {
nonisolated struct LocationRoomTimelineItemContent: Hashable {
let body: String
let geoURI: GeoURI?
let kind: StaticLocationKind
@@ -22,7 +22,7 @@ struct LocationRoomTimelineItemContent: Hashable {
}
}
enum StaticLocationKind {
nonisolated enum StaticLocationKind {
case sender
case pin
@@ -8,7 +8,7 @@
import UIKit
struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
nonisolated struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -8,7 +8,7 @@
import UIKit
struct NoticeRoomTimelineItemContent: Hashable {
nonisolated struct NoticeRoomTimelineItemContent: Hashable {
let body: String
var formattedBody: AttributedString?
}
@@ -9,7 +9,7 @@
import Algorithms
import UIKit
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
nonisolated struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -8,7 +8,7 @@
import UIKit
struct TextRoomTimelineItemContent: Hashable {
nonisolated struct TextRoomTimelineItemContent: Hashable {
let body: String
var formattedBody: AttributedString?
/// The original textual representation of the formatted body directly from the event (usually HTML code)
@@ -9,7 +9,7 @@
import UIKit
import UniformTypeIdentifiers
struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
nonisolated struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -9,7 +9,7 @@
import Foundation
import UniformTypeIdentifiers
struct VideoRoomTimelineItemContent: Hashable {
nonisolated struct VideoRoomTimelineItemContent: Hashable {
let filename: String
var caption: String?
var formattedCaption: AttributedString?
@@ -8,7 +8,7 @@
import Foundation
struct VoiceMessageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
nonisolated struct VoiceMessageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isOutgoing: Bool
@@ -8,7 +8,7 @@
import Foundation
struct CallInviteRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
nonisolated struct CallInviteRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isEditable: Bool
@@ -8,7 +8,7 @@
import Foundation
struct CallNotificationRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
nonisolated struct CallNotificationRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
let isEditable: Bool
@@ -8,7 +8,7 @@
import Foundation
struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Equatable {
nonisolated struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let items: [RoomTimelineItemProtocol]
let itemIDs: [TimelineItemIdentifier]
@@ -8,7 +8,7 @@
import UIKit
struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
nonisolated struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
enum EncryptionType: Hashable {
case megolmV1AesSha2(sessionID: String, cause: UTDCause)
case olmV1Curve25519AesSha2(senderKey: String)
@@ -8,7 +8,7 @@
import Foundation
struct PollRoomTimelineItem: Equatable, EventBasedTimelineItemProtocol {
nonisolated struct PollRoomTimelineItem: Equatable, EventBasedTimelineItemProtocol {
let id: TimelineItemIdentifier
let poll: Poll
let body: String
@@ -20,7 +20,7 @@ struct PollRoomTimelineItem: Equatable, EventBasedTimelineItemProtocol {
var properties: RoomTimelineItemProperties
}
struct Poll: Hashable {
nonisolated struct Poll: Hashable {
let question: String
let kind: Kind
let maxSelections: Int
@@ -9,7 +9,7 @@
import Foundation
import UIKit
struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
nonisolated struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let body: String
let timestamp: Date
@@ -8,7 +8,7 @@
import UIKit
struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
nonisolated struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let body: String
let timestamp: Date
@@ -8,7 +8,7 @@
import UIKit
struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
nonisolated struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let body: String
let timestamp: Date
@@ -8,7 +8,7 @@
import UIKit
struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
nonisolated struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let body: String
@@ -8,7 +8,7 @@
import Foundation
struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
nonisolated struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
enum Position {
@@ -8,6 +8,6 @@
import Foundation
struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
nonisolated struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
}
@@ -8,7 +8,7 @@
import Foundation
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
nonisolated struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
let id: TimelineItemIdentifier
let timestamp: Date
}
@@ -8,7 +8,7 @@
import Foundation
struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
nonisolated struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
// Using a static identifier makes the animations consistent in SwiftUI
let id: TimelineItemIdentifier = .virtual(uniqueID: .init("TimelineStart"))
let name: String?
@@ -9,7 +9,7 @@
import MatrixRustSDK
import UIKit
struct RoomStateEventStringBuilder {
nonisolated struct RoomStateEventStringBuilder {
let userID: String
func buildString(for change: MembershipChange?,
@@ -10,7 +10,7 @@ import MatrixRustSDK
import UIKit
import UniformTypeIdentifiers
struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
nonisolated struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
private let attributedStringBuilder: AttributedStringBuilderProtocol
private let stateEventStringBuilder: RoomStateEventStringBuilder
@@ -927,7 +927,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
}
}
private extension EmbeddedEventDetails {
private nonisolated extension EmbeddedEventDetails {
var isThreaded: Bool {
switch self {
case .ready(.msgLike(let messageLikeContent), _, _, _, _):
@@ -938,7 +938,7 @@ private extension EmbeddedEventDetails {
}
}
private extension Receipt {
private nonisolated extension Receipt {
var dateTimestamp: Date? {
guard let timestamp else {
return nil
@@ -9,7 +9,7 @@
import Foundation
import MatrixRustSDK
protocol RoomTimelineItemFactoryProtocol {
nonisolated protocol RoomTimelineItemFactoryProtocol: Sendable {
func buildTimelineItem(for eventItemProxy: EventTimelineItemProxy, isDM: Bool) -> RoomTimelineItemProtocol?
func buildTimelineItemReply(_ details: MatrixRustSDK.InReplyToDetails) -> TimelineItemReply
}
@@ -9,11 +9,11 @@
import Foundation
import UIKit
protocol RoomTimelineItemProtocol {
nonisolated protocol RoomTimelineItemProtocol: Sendable {
var id: TimelineItemIdentifier { get }
}
extension RoomTimelineItemProtocol {
nonisolated extension RoomTimelineItemProtocol {
var isReactable: Bool {
guard let eventItem = self as? EventBasedTimelineItemProtocol else { return false }
return !eventItem.isRedacted && !eventItem.hasFailedToSend && !eventItem.hasFailedDecryption
@@ -8,4 +8,4 @@
import Foundation
protocol TextBasedRoomTimelineItem: EventBasedMessageTimelineItemProtocol { }
nonisolated protocol TextBasedRoomTimelineItem: EventBasedMessageTimelineItemProtocol { }