diff options
| author | Christian Cleberg <[email protected]> | 2026-07-22 21:43:42 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-22 23:23:11 -0500 |
| commit | 872583eca8b4e7ae6ef85917604d4a5257b9d125 (patch) | |
| tree | 0df88e0621e195f6adf5d0c67615f2099759a530 /DomainDigShareExtension/ShareViewController.swift | |
| parent | d3af0e7d51c2bac5de801bfbbed96a105c442e64 (diff) | |
| download | domain-dig-872583eca8b4e7ae6ef85917604d4a5257b9d125.tar.gz domain-dig-872583eca8b4e7ae6ef85917604d4a5257b9d125.tar.bz2 domain-dig-872583eca8b4e7ae6ef85917604d4a5257b9d125.zip | |
fix: adopt Swift 6 language mode; resolve all concurrency issues (#27)
All three product targets (app, widget, share extension) now build under
SWIFT_VERSION = 6.0 with zero errors and zero warnings. The UITests
target stays on 5.0: XCTestCase's nonisolated setUp/init overrides
conflict with the target's MainActor default isolation under 6, and test
tooling is not shipping code.
The original seven diagnostics, plus the layers Swift 6 mode surfaced
once those cleared:
- SMTPChannel is an actor. It was implicitly MainActor while running its
receive loop on a background queue, so parsedLines/lineWaiters/
receiveBuffer were declared main-actor-protected and mutated off it —
concurrent mutation while resuming a CheckedContinuation can
double-resume, which traps. The actor serialises all state; Network
callbacks hop in via Task. The start() continuation also gains an
OSAllocatedUnfairLock resume-once guard: the state handler can fire
.ready and later .failed, and resuming twice was a pre-existing trap of
the same family.
- CachedLookupResult is nonisolated (a value pair built inside actor
LookupRuntime cannot have a MainActor-bound memberwise init) with
conditional Sendable — opting out of MainActor isolation also opted out
of the implicit Sendable that globally-isolated types get.
- PortScanService.printableBanner is nonisolated: a pure transformation
called from the connection's queue.
- SweepActivityController stores the activity's Sendable id instead of
the non-Sendable Activity, re-resolving via Activity.activities inside
each fire-and-forget task, so nothing non-Sendable crosses isolation.
- App Intents' static title/description/openAppWhenRun become lets
(get-only protocol requirements; static var is shared mutable global
state), and the summary helpers are @MainActor to match the model
properties they read and the perform() implementations that call them.
- ExternalDataService's ISO8601DateFormatter is nonisolated(unsafe),
citing Apple's documented thread-safety, rather than risking a parser
behaviour change by switching APIs with no test coverage.
- TaskMetricsDelegate.metrics is nonisolated(unsafe): written on the
session's delegate queue, read only after the request completes, and
URLSession guarantees didFinishCollecting precedes task completion.
- The share extension extracts the host via async/withCheckedContinuation
instead of sending a non-Sendable completion into loadItem's @Sendable
handler; Task inherits the view controller's MainActor so the manual
DispatchQueue.main hop goes too.
Validated: clean Swift 6 build of all product targets, and the full
enforced 11-test audit suite green on the floor runtime — Swift 6's
runtime isolation checks ran the app through every screen without a
trap.
Diffstat (limited to 'DomainDigShareExtension/ShareViewController.swift')
| -rw-r--r-- | DomainDigShareExtension/ShareViewController.swift | 26 |
1 files changed, 15 insertions, 11 deletions
diff --git a/DomainDigShareExtension/ShareViewController.swift b/DomainDigShareExtension/ShareViewController.swift index 411f9e7..1243892 100644 --- a/DomainDigShareExtension/ShareViewController.swift +++ b/DomainDigShareExtension/ShareViewController.swift @@ -24,14 +24,17 @@ final class ShareViewController: UIViewController { label.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -24) ]) - extractSharedDomain { [weak self] domain in - DispatchQueue.main.async { - self?.finish(domain: domain) - } + // Task inherits this view controller's MainActor context, so finish() + // lands back on main without a manual dispatch. The continuation form + // also avoids sending a non-Sendable completion into loadItem's + // @Sendable handler. (#27) + Task { [weak self] in + let domain = await self?.extractSharedDomain() + self?.finish(domain: domain ?? nil) } } - private func extractSharedDomain(completion: @escaping (String?) -> Void) { + private func extractSharedDomain() async -> String? { let providers = (extensionContext?.inputItems ?? []) .compactMap { $0 as? NSExtensionItem } .flatMap { $0.attachments ?? [] } @@ -39,14 +42,15 @@ final class ShareViewController: UIViewController { guard let provider = providers.first(where: { $0.hasItemConformingToTypeIdentifier(UTType.url.identifier) }) else { - completion(nil) - return + return nil } - provider.loadItem(forTypeIdentifier: UTType.url.identifier) { item, _ in - let url = item as? URL ?? (item as? Data).flatMap { URL(dataRepresentation: $0, relativeTo: nil) } - let host = url?.host?.trimmingCharacters(in: .whitespacesAndNewlines) - completion(host?.isEmpty == false ? host : nil) + return await withCheckedContinuation { continuation in + provider.loadItem(forTypeIdentifier: UTType.url.identifier) { item, _ in + let url = item as? URL ?? (item as? Data).flatMap { URL(dataRepresentation: $0, relativeTo: nil) } + let host = url?.host?.trimmingCharacters(in: .whitespacesAndNewlines) + continuation.resume(returning: host?.isEmpty == false ? host : nil) + } } } |
