diff --git a/Package.swift b/Package.swift index 7fa619d..74c4956 100644 --- a/Package.swift +++ b/Package.swift @@ -8,16 +8,31 @@ let settings: [SwiftSetting] = [ let package = Package( name: "MainOffender", - platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)], + platforms: [ + .macOS(.v10_15), + .macCatalyst(.v13), + .iOS(.v13), + .tvOS(.v13), + .watchOS(.v6), + ], products: [ .library(name: "MainOffender", targets: ["MainOffender"]), ], targets: [ - .target(name: "MainOffender", swiftSettings: settings), + .target(name: "MainOffender"), .testTarget( name: "MainOffenderTests", - dependencies: ["MainOffender"], - swiftSettings: settings + dependencies: ["MainOffender"] ), ] ) + +let swiftSettings: [SwiftSetting] = [ + .enableExperimentalFeature("StrictConcurrency"), +] + +for target in package.targets { + var settings = target.swiftSettings ?? [] + settings.append(contentsOf: swiftSettings) + target.swiftSettings = settings +} diff --git a/README.md b/README.md index 042d272..14119fd 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,8 @@ A tiny package with utilities to help with Swift Concurrency You can also just copy-paste the stuff you need into your project if you aren't into taking on the dependency. I won't be offended (ha!). Features: -- `MainActor.runUnsafely` for `MainActor.assumeIsolated` with lower OS version requirements. - `DispatchQueue.mainActor` for a `DispatchQueue` proxy that is `@MainActor`-compatible -- `OperationQueue.mainActor` for an `OperationQueue` proxy that is `@MainActor` compatible +- `OperationQueue.mainActor` for an `OperationQueue` proxy that is `@MainActor`-compatible - `UnsafeBlockOperation` for `BlockOperation` without `Sendable` checking - Additions to `OperationQueue` to submit blocks directly without `Sendable` checking - `addUnsafeObserver(forName:object:queue:using:)` for `NotificationCenter` diff --git a/Sources/MainOffender/MainActor+RunUnsafely.swift b/Sources/MainOffender/MainActor+RunUnsafely.swift index 30d24d7..824c4cd 100644 --- a/Sources/MainOffender/MainActor+RunUnsafely.swift +++ b/Sources/MainOffender/MainActor+RunUnsafely.swift @@ -1,20 +1,12 @@ import Foundation -public extension MainActor { +extension MainActor { /// Execute the given body closure on the main actor without enforcing MainActor isolation. /// /// It will crash if run on any non-main thread. + @available(*, deprecated, message: "Please move to assumeIsolated") @_unavailableFromAsync - static func runUnsafely(_ body: @MainActor () throws -> T) rethrows -> T { -#if swift(>=5.9) - if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { - return try MainActor.assumeIsolated(body) - } -#endif - - dispatchPrecondition(condition: .onQueue(.main)) - return try withoutActuallyEscaping(body) { fn in - try unsafeBitCast(fn, to: (() throws -> T).self)() - } + public static func runUnsafely(_ body: @MainActor () throws -> T) rethrows -> T where T : Sendable{ + try MainActor.assumeIsolated(body) } } diff --git a/Sources/MainOffender/MainDispatchQueue.swift b/Sources/MainOffender/MainDispatchQueue.swift index f48614e..e843f52 100644 --- a/Sources/MainOffender/MainDispatchQueue.swift +++ b/Sources/MainOffender/MainDispatchQueue.swift @@ -8,7 +8,7 @@ public final class MainDispatchQueue: Sendable { execute work: @MainActor @escaping @Sendable @convention(block) () -> Void ) { DispatchQueue.main.async(group: group, qos: qos, flags: flags) { - MainActor.runUnsafely(work) + MainActor.assumeIsolated(work) } } @@ -21,7 +21,7 @@ public final class MainDispatchQueue: Sendable { let work = unsafeBitCast(unsafeWork, to: (@Sendable @convention(block) () -> Void).self) DispatchQueue.main.async(group: group, qos: qos, flags: flags) { - MainActor.runUnsafely(work) + MainActor.assumeIsolated(work) } } @@ -32,7 +32,7 @@ public final class MainDispatchQueue: Sendable { execute work: @MainActor @escaping @Sendable @convention(block) () -> Void ) { DispatchQueue.main.asyncAfter(deadline: deadline, qos: qos, flags: flags) { - MainActor.runUnsafely(work) + MainActor.assumeIsolated(work) } } @@ -45,7 +45,7 @@ public final class MainDispatchQueue: Sendable { let work = unsafeBitCast(unsafeWork, to: (@Sendable @convention(block) () -> Void).self) DispatchQueue.main.asyncAfter(deadline: deadline, qos: qos, flags: flags) { - MainActor.runUnsafely(work) + MainActor.assumeIsolated(work) } } } @@ -62,7 +62,7 @@ extension DispatchGroup { execute work: @MainActor @escaping @Sendable @convention(block) () -> Void ) { self.notify(qos: qos, flags: flags, queue: .main) { - MainActor.runUnsafely(work) + MainActor.assumeIsolated(work) } } } diff --git a/Sources/MainOffender/MainOperationQueue.swift b/Sources/MainOffender/MainOperationQueue.swift index 8d8d94f..1b9269a 100644 --- a/Sources/MainOffender/MainOperationQueue.swift +++ b/Sources/MainOffender/MainOperationQueue.swift @@ -6,25 +6,25 @@ import Foundation public final class MainOperationQueue: Sendable { public func addOperation(_ block: @MainActor @escaping @Sendable () -> Void) { OperationQueue.main.addOperation { - MainActor.runUnsafely(block) + MainActor.assumeIsolated(block) } } public func addUnsafeOperation(_ unsafeBlock: @escaping () -> Void) { OperationQueue.main.addUnsafeOperation { - MainActor.runUnsafely(unsafeBlock) + MainActor.assumeIsolated(unsafeBlock) } } public func addBarrierBlock(_ barrier: @MainActor @escaping @Sendable () -> Void) { OperationQueue.main.addBarrierBlock { - MainActor.runUnsafely(barrier) + MainActor.assumeIsolated(barrier) } } public func addUnsafeBarrierBlock(_ unsafeBlock: @escaping () -> Void) { OperationQueue.main.addUnsafeBarrierBlock { - MainActor.runUnsafely(unsafeBlock) + MainActor.assumeIsolated(unsafeBlock) } } diff --git a/Sources/MainOffender/UnsafeBlockOperation.swift b/Sources/MainOffender/UnsafeBlockOperation.swift index 960184d..6676231 100644 --- a/Sources/MainOffender/UnsafeBlockOperation.swift +++ b/Sources/MainOffender/UnsafeBlockOperation.swift @@ -1,7 +1,7 @@ import Foundation /// An operation that manages the concurrent execution of one or more blocks without Sendable checking. -public class UnsafeBlockOperation: BlockOperation { +public class UnsafeBlockOperation: BlockOperation, @unchecked Sendable { public init(block unsafeBlock: @escaping () -> Void) { super.init() addUnsafeExecutionBlock(block: unsafeBlock) diff --git a/Tests/MainOffenderTests/MainActorTests.swift b/Tests/MainOffenderTests/MainActorTests.swift index 1d90135..7ef0faa 100644 --- a/Tests/MainOffenderTests/MainActorTests.swift +++ b/Tests/MainOffenderTests/MainActorTests.swift @@ -1,6 +1,7 @@ import XCTest import MainOffender +@available(*, deprecated, message: "Here just to validate the legacy API") final class MainActorTests: XCTestCase { @MainActor func testRunOnMainUnsafely() throws { diff --git a/Tests/MainOffenderTests/UnsafeBlockOperationTests.swift b/Tests/MainOffenderTests/UnsafeBlockOperationTests.swift index 3376f2a..e3b4570 100644 --- a/Tests/MainOffenderTests/UnsafeBlockOperationTests.swift +++ b/Tests/MainOffenderTests/UnsafeBlockOperationTests.swift @@ -41,18 +41,13 @@ final class UnsafeBlockOperationTests: XCTestCase { } func testUnsafeBarrierBlock() async throws { - let unsendable = UnsendableClass() - let queue = OperationQueue() let exp = expectation(description: "completed") queue.addBarrierBlock { - unsendable.value = 0 exp.fulfill() } await fulfillment(of: [exp], timeout: 1.0) - - XCTAssertEqual(unsendable.value, 0) } }