From 7b9012d0093d0f47b1aaae489bda67dcadc87735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20Vaculi=CC=81k?= Date: Sat, 15 Aug 2026 16:28:16 +0200 Subject: [PATCH] Isolate the UIKit-facing API to the main actor --- Package.swift | 2 +- Sources/CellKit/CellConfigurable.swift | 1 + Sources/CellKit/CellConvertible.swift | 1 + Sources/CellKit/CellModel.swift | 2 ++ Sources/CellKit/CellModelDataSource.swift | 2 ++ Sources/CellKit/CellModelSelectable.swift | 1 + Sources/CellKit/DataSource.swift | 2 ++ .../CollectionSupplementaryViewModel.swift | 1 + .../DiffableDataSources/LazyCellProvider.swift | 1 + .../LazySupplementaryViewProvider.swift | 1 + Sources/CellKit/ReusableView.swift | 1 + .../DifferentiableCellModel.swift | 18 +++++++++++++----- .../DifferentiableCellModelDataSource.swift | 1 + 13 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Package.swift b/Package.swift index bc0094b..7b9c6a0 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "CellKit", - platforms: [.iOS(.v9), .tvOS(.v9)], + platforms: [.iOS(.v13), .tvOS(.v13)], products: [ .library( name: "CellKit", diff --git a/Sources/CellKit/CellConfigurable.swift b/Sources/CellKit/CellConfigurable.swift index 1c120ae..8302813 100644 --- a/Sources/CellKit/CellConfigurable.swift +++ b/Sources/CellKit/CellConfigurable.swift @@ -1,3 +1,4 @@ +@MainActor public protocol CellConfigurable: AnyObject { associatedtype Model func configure(with model: Model) diff --git a/Sources/CellKit/CellConvertible.swift b/Sources/CellKit/CellConvertible.swift index dd894cc..bcc5424 100644 --- a/Sources/CellKit/CellConvertible.swift +++ b/Sources/CellKit/CellConvertible.swift @@ -2,6 +2,7 @@ // CellConvertible.swift public typealias ReusableCellConvertible = CellConvertible & ReusableView +@MainActor public protocol CellConvertible { associatedtype Cell: CellConfigurable } diff --git a/Sources/CellKit/CellModel.swift b/Sources/CellKit/CellModel.swift index c3729e8..b8fe135 100644 --- a/Sources/CellKit/CellModel.swift +++ b/Sources/CellKit/CellModel.swift @@ -1,3 +1,4 @@ +@MainActor public protocol CellModel: ReusableView { var cellHeight: Double { get } var highlighting: Bool { get } @@ -20,6 +21,7 @@ public extension CellModel { } } +@MainActor public protocol SupplementaryViewModel: ReusableView { var height: Double { get } diff --git a/Sources/CellKit/CellModelDataSource.swift b/Sources/CellKit/CellModelDataSource.swift index b0780bf..0148b03 100644 --- a/Sources/CellKit/CellModelDataSource.swift +++ b/Sources/CellKit/CellModelDataSource.swift @@ -1,9 +1,11 @@ import struct Foundation.IndexPath +@MainActor public protocol CellModelDataSourceDelegate: AnyObject { func didSelectCellModel(_ cellModel: CellModel, at indexPath: IndexPath) } +@MainActor open class CellModelDataSource: AbstractDataSource, DataSource { public var sections: [CellModelSection] diff --git a/Sources/CellKit/CellModelSelectable.swift b/Sources/CellKit/CellModelSelectable.swift index b41818c..1971348 100644 --- a/Sources/CellKit/CellModelSelectable.swift +++ b/Sources/CellKit/CellModelSelectable.swift @@ -1,3 +1,4 @@ +@MainActor public protocol CellModelSelectable { func didSelect() } diff --git a/Sources/CellKit/DataSource.swift b/Sources/CellKit/DataSource.swift index e897be7..3e4cc85 100644 --- a/Sources/CellKit/DataSource.swift +++ b/Sources/CellKit/DataSource.swift @@ -1,5 +1,6 @@ import UIKit +@MainActor public protocol DataSource { associatedtype Section @@ -9,6 +10,7 @@ public protocol DataSource { subscript(index: Int) -> Section { get } } +@MainActor open class AbstractDataSource: NSObject { public weak var delegate: CellModelDataSourceDelegate? diff --git a/Sources/CellKit/DiffableDataSources/CollectionSupplementaryViewModel.swift b/Sources/CellKit/DiffableDataSources/CollectionSupplementaryViewModel.swift index 261f90d..b7b741f 100644 --- a/Sources/CellKit/DiffableDataSources/CollectionSupplementaryViewModel.swift +++ b/Sources/CellKit/DiffableDataSources/CollectionSupplementaryViewModel.swift @@ -1,4 +1,5 @@ @available(iOS 13.0, tvOS 13.0, *) +@MainActor public protocol CollectionSupplementaryViewModel: ReusableView { var kind: SupplementaryElementKind { get } diff --git a/Sources/CellKit/DiffableDataSources/LazyCellProvider.swift b/Sources/CellKit/DiffableDataSources/LazyCellProvider.swift index 0c3da50..dc61d49 100644 --- a/Sources/CellKit/DiffableDataSources/LazyCellProvider.swift +++ b/Sources/CellKit/DiffableDataSources/LazyCellProvider.swift @@ -1,6 +1,7 @@ import UIKit @available(iOS 13.0, tvOS 13.0, *) +@MainActor public final class LazyCellProvider { private var registeredIdentifiers: Set diff --git a/Sources/CellKit/DiffableDataSources/LazySupplementaryViewProvider.swift b/Sources/CellKit/DiffableDataSources/LazySupplementaryViewProvider.swift index fbdc256..ade4a13 100644 --- a/Sources/CellKit/DiffableDataSources/LazySupplementaryViewProvider.swift +++ b/Sources/CellKit/DiffableDataSources/LazySupplementaryViewProvider.swift @@ -1,6 +1,7 @@ import UIKit @available(iOS 13.0, tvOS 13.0, *) +@MainActor public final class LazySupplementaryViewProvider { private var registeredIdentifiers: [String: Set] diff --git a/Sources/CellKit/ReusableView.swift b/Sources/CellKit/ReusableView.swift index 2161d58..6061909 100644 --- a/Sources/CellKit/ReusableView.swift +++ b/Sources/CellKit/ReusableView.swift @@ -1,6 +1,7 @@ import class Foundation.Bundle import class UIKit.UINib +@MainActor public protocol ReusableView { var registersLazily: Bool { get } var usesNib: Bool { get } diff --git a/Sources/DiffableCellKit/DifferentiableCellModel.swift b/Sources/DiffableCellKit/DifferentiableCellModel.swift index fb8e836..53e3763 100644 --- a/Sources/DiffableCellKit/DifferentiableCellModel.swift +++ b/Sources/DiffableCellKit/DifferentiableCellModel.swift @@ -4,6 +4,7 @@ import CellKit #endif /// Support for determining whether cell model belongs to a certain cell, whether cell should be inserted, removed, updated or moved. +@MainActor public protocol DifferentiableCellModel: CellModel { /// Identifier of a cell model inside it's own domain determined by reusable identifier. Assuming model is already presented inside a view, changing of this value will result in it's removal and insertion into the view. @@ -31,14 +32,21 @@ extension DifferentiableCellModel where Self: Equatable { struct DifferentiableCellModelWrapper { let cellModel: DifferentiableCellModel + let differenceIdentifier: String + + @MainActor + init(cellModel: DifferentiableCellModel) { + self.cellModel = cellModel + self.differenceIdentifier = "\(cellModel.reuseIdentifier)<.>\(cellModel.domainIdentifier)" + } } extension DifferentiableCellModelWrapper: Equatable, Differentiable { static func == (lhs: DifferentiableCellModelWrapper, rhs: DifferentiableCellModelWrapper) -> Bool { - lhs.cellModel.hasEqualContent(with: rhs.cellModel) - } - - var differenceIdentifier: String { - "\(cellModel.reuseIdentifier)<.>\(cellModel.domainIdentifier)" + // DifferenceKit requires a nonisolated `==`, but wrappers are only ever created and + // diffed by `DifferentiableCellModelDataSource`, which is main actor-isolated. + MainActor.assumeIsolated { + lhs.cellModel.hasEqualContent(with: rhs.cellModel) + } } } diff --git a/Sources/DiffableCellKit/DifferentiableCellModelDataSource.swift b/Sources/DiffableCellKit/DifferentiableCellModelDataSource.swift index 27d8ab5..323fc46 100644 --- a/Sources/DiffableCellKit/DifferentiableCellModelDataSource.swift +++ b/Sources/DiffableCellKit/DifferentiableCellModelDataSource.swift @@ -4,6 +4,7 @@ import UIKit import CellKit #endif +@MainActor open class DifferentiableCellModelDataSource: AbstractDataSource, DataSource { private enum Container {