Skip to content

Comply with Swift 6 concurrency#728

Draft
stevenzeck wants to merge 52 commits intoreadium:developfrom
stevenzeck:swift6
Draft

Comply with Swift 6 concurrency#728
stevenzeck wants to merge 52 commits intoreadium:developfrom
stevenzeck:swift6

Conversation

@stevenzeck
Copy link
Contributor

@stevenzeck stevenzeck commented Feb 19, 2026

This still needs more testing and a lot of review. But I want to get it out there for visibility. This PR updates swift-build-tools from 5.10 to 6.0. In doing so, strict concurrency is enabled, thus why a lot of changes are needed here.

Swift 6 Strict Concurrency Migration Guide

The vast majority of changes focus on Swift 6 Strict Concurrency adoption. This involves migrating classes to actors, enforcing @MainActor isolation on UI/Navigator components, adding Sendable conformance to protocols and types, and requiring closure callbacks to be @Sendable.

This guide has been updated to cover all comprehensive API changes, including the introduction of type-safe JSON handling and asynchronous network delegate methods.


1. Concurrency Isolation Changes

Navigators & UI are now @MainActor

To ensure thread safety for UI-related operations, the entire Navigator subsystem, input handlers, and view controllers are now isolated to the main thread.

Protocols: Navigator, VisualNavigator, DecorableNavigator, EPUBNavigatorViewModelDelegate, AudioSessionUser, PreferencesEditor, Configurable, EPUBSpreadViewDelegate, PaginationViewDelegate, AVTTSEngineDelegate, PublicationSpeechSynthesizerDelegate

Classes: AudioNavigator, EPUBFixedSpreadView, EPUBReflowableSpreadView, EPUBSpreadView, PDFDocumentHolder, PDFDocumentView, PDFTapGestureController, AVTTSEngine, PublicationSpeechSynthesizer, NowPlayingInfo, EPUBNavigatorViewController, EditingActionsController, InputObservableViewController

Helpers: DirectionalNavigationAdapter, CompletionList, TargetAction

Input Events: Initialization methods for Pointer, KeyEvent, Key, MouseButtons, and KeyModifiers that take UIKit types (UITouch, UIEvent, UIPress) are now explicitly @MainActor.

Impact: You must access these instances and their properties/methods from the Main Actor.

// Old
navigator.goForward()

// New (Swift 6)
await navigator.goForward()
// OR
MainActor.assumeIsolated { navigator.goForward() }

Classes Converted to actor

Service classes and resources handling internal mutable state have been converted to actor to guarantee thread safety.

  • Logger — Previously a final class. Note: sharedInstance is now a static let, and its configuration methods are nonisolated but dispatch internally.
  • GeneratedCoverService — Previously a final class
  • PerResourcePositionsService — Previously a final class
  • DefaultHTTPClient — Previously a final class
  • BufferingResource — Previously a class
  • CachingResource — Previously a class
  • TailCachingResource — Previously a class
  • DataResource — Previously a class
  • HTTPResource — Previously a class
  • FormatSnifferBlob — Previously a class
  • EPUBPositionsService — Previously a class

Impact: Synchronous access to properties or methods on these types must now be awaited.

// Old
let cover = coverService.cover()

// New
let cover = await coverService.cover()

2. Protocol Changes (Sendable Conformance)

The following protocols now inherit from Sendable. Implementations of these protocols must now be thread-safe.

Networking & Data: HTTPClient, HTTPServer, Resource, Streamer, AssetProtocol, Container, URLConvertible, Streamable

Services: PublicationService, ContentProtection, UserRights, LCPClient, LCPLicenseRepository, LCPPassphraseRepository, LCPAuthenticating

Parsing & Format: PublicationParser, XMLDocument, XMLNode, XMLElement, ResourceFactory, ArchiveOpener, PDFDocument, PDFDocumentFactory, FormatSniffer (including HintsFormatSniffer and ContentFormatSniffer)

Logging: LoggerType

Delegates: DefaultHTTPClientDelegate

Impact: If you have custom implementations of these protocols (e.g., a custom HTTPClient), you must ensure they are thread-safe and mark them as Sendable (or @unchecked Sendable).


3. Closure Signature & Async Changes

Almost all closures used in callbacks, factories, and asynchronous operations are now required to be @Sendable. Additionally, some closures and delegate methods have become async.

HTTP/Networking:

  • HTTPClient.stream(request:consume:) — The consume closure is now both async and @Sendable.
  • DefaultHTTPClientDelegatehttpClient(_:request:didReceiveResponse:) and httpClient(_:request:didFailWithError:) are now async.

Parsing:

  • OPDSParser.parseURL (and related v1/v2 parsers) — completion is @Sendable.
  • Publication.Builder.Transform — is @Sendable.

Services:

  • LCPService.acquirePublicationonProgress is @Sendable.
  • TTSEngine.speakonSpeakRange is @Sendable.
  • All Service Factories (e.g., CoverServiceFactory, LocatorServiceFactory, PositionsServiceFactory, ContentProtectionServiceFactory) are now @Sendable.

Resources & Tokenizers:

  • Streamable.streamconsume is @Sendable.
  • Container.maptransform is @Sendable.
  • ResourceTransformer and Tokenizer are now @Sendable.

Impact: You cannot capture non-sendable mutable state in these closures anymore, and you must use await where new async boundaries have been introduced.

// Old
var counter = 0
httpClient.stream(request: req) { data, _ in
    counter += 1 // Error: Capture of 'counter' with non-sendable type 'Int' in a `@Sendable` closure
    return .success(())
}

4. Type Definition Changes

Type-Safe JSON Parsing (JSONValue)

APIs handling arbitrary JSON data have moved away from Any to strictly enforce concurrency safety. The previous approach of using any Sendable for JSON has been replaced entirely by a new type-safe JSONValue enum.

  • JSONDictionary.Value is now JSONValue.
  • Publication and OPDS models (Manifest, Locator, Link, Metadata, OPDSAcquisition, etc.) now return [String: JSONValue] for their .json properties instead of [String: Any].

UI-Bound Models (EditingAction)

Models meant to be passed across concurrency domains have been stripped of non-Sendable UIKit types.

  • EditingAction.Kind changed from .custom(UIMenuItem) to .custom(title: String, action: Selector). UIMenuItem generation is deferred to a @MainActor property.

Networking Models

  • HTTPRequest.userInfo — The value type has changed from AnyHashable to any Sendable.

CSS Properties

CSS property types in CSSUserProperties and CSSRSProperties have changed from concrete generics (e.g., CSSLength?) to existentials (e.g., (any CSSLength)?).

Utilities

  • Weak<T> and _Strong<T> — Now utilize an internal NSLock to achieve @unchecked Sendable conformance, making them safe to pass across concurrency boundaries.

Finalized Classes

The following classes are now final or actor, preventing subclassing:

  • LCPDialogAuthentication
  • LCPPassphraseAuthentication
  • ReadiumWebPubParser

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request migrates the Readium Swift toolkit from Swift 5.10 to Swift 6.0, enabling strict concurrency checking. The migration involves converting classes to actors, adding @MainActor isolation to UI components, making protocols and types Sendable, and updating closures to be @Sendable. The changes are comprehensive and affect the entire codebase including tests.

Changes:

  • Migrated swift-tools-version from 5.10 to 6.0 in Package.swift
  • Converted service classes (Logger, GeneratedCoverService, PerResourcePositionsService, etc.) to actors for thread safety
  • Isolated UI/Navigator components to MainActor to ensure thread-safe UI operations
  • Added Sendable conformance to protocols and types throughout the codebase
  • Updated closure signatures to be @sendable for concurrency safety
  • Changed JSONDictionary and properties from Any to any Sendable for type safety
  • Refactored resource streaming and caching implementations for concurrency
  • Removed deprecated tokenizer implementations (NS and Simple text tokenizers)

Reviewed changes

Copilot reviewed 220 out of 220 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Package.swift Updated Swift tools version from 5.10 to 6.0
Tests/**/*.swift Updated test fixtures and helpers to use any Sendable, changed #file to #filePath, removed deprecated tokenizer tests
Sources/Shared/Toolkit/**/*.swift Added Sendable conformance to core types, protocols, and data structures
Sources/Shared/Publication/**/*.swift Updated JSON dictionary types to any Sendable, added Sendable conformance
Sources/Streamer/**/*.swift Converted services to actors, made factories @sendable
Sources/Navigator/**/*.swift Added @mainactor isolation to UI components and navigators
Sources/Navigator/TTS/*.swift Made TTS components MainActor-isolated, updated delegate callbacks
Sources/LCP/**/*.swift Added Sendable conformance and thread-safety mechanisms
Sources/Adapters/**/*.swift Added @unchecked Sendable and @preconcurrency imports

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants