Adopt Sendable across PDF, LCP and Container APIs#803
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR tightens Swift Concurrency guarantees across the toolkit by adopting Sendable widely, updating PDF-related APIs to require Sendable URL inputs, and refactoring mutable/shared state to use Mutex (or actor/main-actor isolation) to satisfy strict concurrency checking.
Changes:
- Adds
Sendableconformance to multiple core protocols/types (e.g.Container,HTTPClient,PDFDocument,UserRights, LCP protocols) and adjusts implementations/mocks accordingly. - Constrains PDF service/factory APIs to
HREF: URLConvertible & Sendableand marksResourceTransformerclosures as@Sendable. - Refactors shared mutable state for concurrency safety (eg.
License.documents, EPUB deobfuscation stream state, test mocks) and isolatesLCPDefaultRenewDelegateto@MainActor.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/StreamerTests/Parser/EPUB/Services/EPUBPositionsServiceTests.swift | Updates a test mock container to satisfy new Sendable requirements. |
| Tests/SharedTests/Toolkit/HTTP/HTTPResourceTests.swift | Makes mock HTTP client Sendable and guards mutable state with Mutex. |
| Tests/SharedTests/Publication/Services/Content/Iterators/PDFResourceContentIteratorTests.swift | Makes PDF document mocks Sendable and synchronizes tracking with Mutex. |
| Tests/SharedTests/Publication/Services/Content Protection/ContentProtectionServiceTests.swift | Makes test UserRights implementation Sendable and synchronizes counters with Mutex. |
| Tests/SharedTests/ProxyContainer.swift | Adjusts stored closure to be @Sendable and updates existential spelling for Resource. |
| Sources/Streamer/Parser/EPUB/Resource Transformers/EPUBDeobfuscator.swift | Synchronizes streaming read position using Mutex for concurrency safety. |
| Sources/Shared/Toolkit/ZIP/ZIPFoundation/ZIPFoundationArchiveFactory.swift | Declares archive factory as Sendable. |
| Sources/Shared/Toolkit/PDF/PDFKit.swift | Adds retroactive unchecked Sendable conformance to PDFKit.PDFDocument and tightens generic constraints. |
| Sources/Shared/Toolkit/PDF/PDFDocumentService.swift | Makes PDF document service Sendable and constrains HREF generics to Sendable. |
| Sources/Shared/Toolkit/PDF/PDFDocument.swift | Makes PDFDocument/PDFDocumentFactory Sendable and constrains HREF generics to Sendable. |
| Sources/Shared/Toolkit/PDF/CGPDF.swift | Tightens HREF constraints and refactors callback state storage to use Mutex. |
| Sources/Shared/Toolkit/HTTP/HTTPContainer.swift | Declares HTTPContainer as Sendable. |
| Sources/Shared/Toolkit/HTTP/HTTPClient.swift | Declares HTTPClient as Sendable. |
| Sources/Shared/Toolkit/Data/Container/TransformingContainer.swift | Makes transformer closures @Sendable and declares container Sendable. |
| Sources/Shared/Toolkit/Data/Container/SingleResourceContainer.swift | Declares container Sendable. |
| Sources/Shared/Toolkit/Data/Container/Container.swift | Makes Container Sendable and updates CompositeContainer to store any Container. |
| Sources/Shared/Publication/Services/Content Protection/UserRights.swift | Makes UserRights Sendable. |
| Sources/Navigator/PDF/PDFNavigatorViewController.swift | Tightens PDF navigator document-opening generic constraints to Sendable HREFs. |
| Sources/LCP/Services/DeviceService.swift | Declares service Sendable and updates dependencies to any existentials. |
| Sources/LCP/License/License.swift | Refactors License to be Sendable by protecting mutable state with Mutex and using weak observer capture. |
| Sources/LCP/LCPRenewDelegate.swift | Makes renew delegate Sendable, isolates default delegate to @MainActor, bridges non-Sendable system delegate with @preconcurrency. |
| Sources/LCP/LCPLicenseRepository.swift | Makes license repository protocol Sendable. |
| Sources/LCP/LCPClient.swift | Makes LCP client protocol Sendable and updates LCPClientContext to Any & Sendable. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sendable across PDF, LCP and Container APIs
mickael-menu
approved these changes
Jun 17, 2026
mickael-menu
left a comment
Member
There was a problem hiding this comment.
Thank you @stevenzeck!
I made a few changes:
- Fixed crash with
EPUBDeobfuscator. Streamed Data chunks can be slices with non-zero start indices; positional mutation on them crashed. - Replaced the global
@retroactive @unchecked Sendableconformance onPDFKit.PDFDocument(which leaked an unsound claim to every instance app-wide) with a privatePDFKitPDFDocumentwrapper that confines the assertion. - Removed redundant
Sendableconformance from types that already inherit it via their protocol (Container,HTTPClient, etc.). I prefer to keep with idiomatic usage (when we conform toHashable, we don't also addEquatable, for example) and SwiftFormat's default behavior (redundantSendablerule which was fixed recently to take into accountpublic).
mickael-menu
pushed a commit
that referenced
this pull request
Jun 24, 2026
mickael-menu
pushed a commit
that referenced
this pull request
Jul 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PDFKit.PDFDocumentandCGPDFDocument) using@retroactive @unchecked Sendable.HREFgeneric parameters across PDF service APIs toURLConvertible & Sendable.Licenseclass to conform toSendableby wrapping parts in aMutexand utilizing weak closures.Mutexsynchronization for mutable state in stream processing (likeEPUBDeobfuscator.readPosition).LCPDefaultRenewDelegateto@MainActorand uses@preconcurrencyto bridge non-Sendablesystem protocols (SFSafariViewControllerDelegate).Public API Breaking Changes
Container,HTTPClient,PDFDocument,PDFDocumentFactory,PDFDocumentService,LCPClient,LCPLicenseRepository,LCPRenewDelegate, andUserRightsall now conform toSendable.LCPClientContexttypealias changed fromAnytoAny & Sendable.HREFgeneric parameters inPDFDocumentFactory.open(resource:at:password:)andPDFDocumentServiceopen methods now requireURLConvertible & Sendable.ResourceTransformerinTransformingContainernow requires@Sendable.LCPDefaultRenewDelegateis now isolated to@MainActor.ProxyContainerinitializer closure parameters now require@Sendable.Relates to #758.