Adopt default main actor isolation and drop CocoaPods - #55
Merged
Conversation
Set `defaultIsolation` to `MainActor` and the Swift 6 language mode for both targets, which makes the whole UIKit-facing API main actor-isolated without a single annotation in the library sources. Platforms move to iOS 15 / tvOS 15 to match FTAPIKit, which makes the `@available(iOS 13.0, tvOS 13.0, *)` annotations redundant. CocoaPods support is removed in favour of the Swift package, so the `#if SWIFT_PACKAGE` guards around `import CellKit` go away too. The Example app is updated to the Swift 6 language mode with default main actor isolation and approachable concurrency, matching how apps consuming CellKit are configured. CI is rewritten to match FTAPIKit: no `pod lib lint`, current runner actions, and a build of both library schemes plus the Example app. The two long-standing SwiftLint violations in DataSource.swift are fixed so `swiftlint --strict` passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member
Author
|
Closing for now — keeping the work on the branch while we settle the approach. |
Replace the two individual SWIFT_UPCOMING_FEATURE_* settings with SWIFT_APPROACHABLE_CONCURRENCY, which is what our projects actually set and which resolves to the same upcoming features in the Swift 6 language mode. Clarify in the README why a main actor-isolated consumer still needs `@MainActor` on each conformance: conformance isolation is not inferred for conformances to protocols that are themselves main actor-isolated, and InferIsolatedConformances does not cover that case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 7, 2026
The Example project had no shared scheme, so `xcodebuild test` never built or ran ExampleUITests. Add Example.xcscheme with the UI test bundle in its Test action. Adopt the UIScene lifecycle: iOS 27 terminates apps that still rely on the legacy `AppDelegate.window` path. Add SceneDelegate and the scene manifest, and drop the now redundant UIMainStoryboardFile. The UI test target inherits SWIFT_DEFAULT_ACTOR_ISOLATION from the project; XCTestCase overrides must stay nonisolated, so set the test target back to nonisolated. Replace the placeholder launch test with three tests that exercise the main actor-isolated DifferentiableCellModelDataSource on a real table view: sections render, tapping a cell updates it through a diff, and inserting and resetting rows diffs correctly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1.3.0 is the only DifferenceKit release with the current API and is what Package.resolved already pins. Dependabot now watches the Swift package and GitHub Actions instead of the removed Gemfile. Note in the README that Swift 6.4 infers the isolated conformances a main actor-isolated consumer otherwise has to write by hand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pick the newest iOS runtime and its first available iPhone simulator at run time so the step keeps working when runner images change, and print the toolchain version up front. Keep the job id `test`, which is the required status check on main. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The cell models declared `cellHeight` as `CGFloat`, which never matched the protocol's `Double` requirement, so every row silently fell back to the 44pt default and the 50pt "Welcome!" label was squeezed out of view. The insert/reset test read the first device cell's label immediately after tapping, racing the animated insert. Wait for the label to change with a predicate expectation instead of assuming it already has. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bump actions/checkout to v7, remove the toolchain diagnostic step, and keep the same lint setup as FTAPIKit (SwiftLint is not preinstalled on macos-latest, so brew install stays). xcodebuild remains in place of `swift build`/`swift test` because CellKit is UIKit-only. Neither FTAPIKit nor FuturedKit ships a dependabot.yml, so remove it here too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ssestak
approved these changes
Sep 7, 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.
Supersedes #54 (left open for its author; see the comment there). Credit to @radimvaculik for the analysis that motivated this.
Motivation
CellKit is a
UITableView/UICollectionViewdata-source layer — every one of its protocols is only ever exercised on the main thread. Under Swift 6 that mismatch surfaces asconformance of 'FooCellModel' to protocol 'CellConvertible' crosses into main actor-isolated codeon every consumer conformance (61 warnings in one app, 45 hand-written@MainActorconformances to silence them).Change
defaultIsolationset toMainActorwith the Swift 6 language mode on both targets (SE-0466; the setting Apple's WWDC25 guidance recommends for UI-focused modules):Zero
@MainActorannotations inSources/. The onlySources/edits are removals: redundant@available(iOS 13, *),#if SWIFT_PACKAGEguards, two long-standing SwiftLint violations.swift-tools-version:6.2→ Xcode 26+.CellKit.podspec,Gemfile,Gemfile.lock). Swift Package only from 1.0.0. RemovingGemfile.lockalso clears the 11 open rubygems security alerts; Bump cocoapods-downloader from 1.3.0 to 1.6.3 #51–Bump activesupport from 4.2.11.3 to 7.2.3.1 #53 were closed as superseded.Package.resolvedpinned).SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, Approachable Concurrency — how our apps are configured. Adopts the UIScene lifecycle (iOS 27 terminates apps that don't). FixescellHeightdeclared asCGFloat— it never satisfied theDoublerequirement, so every row silently fell back to 44pt.xcodebuild testhad never builtExampleUITests. AddedExample.xcscheme, and replaced theXCTAssert(true)placeholder with three tests that drive the main actor-isolatedDifferentiableCellModelDataSourceon a real table: sections render; tapping a cell updates it through a diff; inserting rows and resetting diffs correctly.ci.yml(actions/checkout@v7,brew install swiftlint,swiftlint --strict), withxcodebuildin place ofswift build/swift testbecause CellKit is UIKit-only: both library builds, then the Example UI tests on a simulator chosen dynamically (newest iOS runtime, first iPhone) so runner image changes don't break it. Job id staystest(the required check onmain).Comparison with #54
#54 annotates each declaration by hand and reworks
DifferentiableCellModelWrapperwithMainActor.assumeIsolated. Building that branch in the Swift 6 language mode still fails inDiffableCellKit:==:sending 'lhs' risks causing data races— non-Sendable wrappers captured into the@MainActorclosure.arraySection:cells.map(DifferentiableCellModelWrapper.init)passes a@MainActorinit as a bare function value — loses global actor 'MainActor'.Container.reloaddoesn't inherit the class's@MainActorand calls UIKit from a nonisolated context.It also leaves
CellKit.podspecat iOS 9, which cannot compileMainActor. WithdefaultIsolationnone of the wrapper rework is needed: SE-0470 isolated conformances let DifferenceKit's nonisolated generic algorithm accept the main actor-isolated wrapper because it is always called from the main actor.Consumer impact
Verified with a real consumer module against this branch:
maintodaynonisolatedisolation (#54's case)SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor@MainActorper CellKit protocol conformed to (Xcode 26); none on Xcode 27 / Swift 6.4, which infers themThe second row is an SE-0470 inference gap for conformances to protocols that are themselves main actor-isolated; it pre-dates this PR and is documented in the README. The Example app shows that configuration.
Verification
swiftlint --strict: 0 violations.xcodebuild buildforCellKitandDiffableCellKit(generic/platform=iOS): 0 warnings, Swift 6 language mode. Verified on Xcode 26.6 / Swift 6.3.3 and Xcode 27.0 beta / Swift 6.4.xcodebuild testExample: 3/3 UI tests pass on iPhone 17 Pro Max (iOS 27.0) and iPhone 16 (iOS 18.6), twice each with retries disabled. The app was also launched on the iPhone 16 simulator and visually checked: nav bar actions, 170pt "Welcome!" cell, "Cell Phones" header, five iPhone and five Android rows at 60pt.swiftcagainst the built module.Breaking
iOS 15+ / tvOS 15+, Xcode 26+ to build, CocoaPods dropped. Any conformance that is not main actor-isolated stops compiling. Ships as 1.0.0 — the README already points at
from: "1.0.0"; the tag is cut right after merge.