Skip to content

mattmassicotte/MainOffender

Build Status Platforms Documentation

MainOffender

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:

  • DispatchQueue.mainActor for a DispatchQueue 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
  • ThreadExecutor that can be used to back an actor with a dedicated thread + runloop
  • RunLoop.turn to better control the runloop in an async context

Usage

Dispatch:

// MainActor proxy
let mainQueue = DispatchQueue.mainActor

mainQueue.async {
    // statically MainActor
}

let group = DispatchGroup()

// an overload to integrate with DispatchGroup
group.notify(queue: mainQueue) {
    // statically MainActor
}

OperationQueue:

// MainActor proxy
let mainQueue = OperationQueue.mainActor

mainQueue.addOperation {
    // statically MainActor
}

// Unsafe variants
let op = UnsafeBlockOperation {
    // non-Sendable captures ok
}

operationQueue.addOperation(op)

queue.addUnsafeOperation {
    // non-Sendable captures ok
}

queue.addUnsafeBarrierBlock {
    // non-Sendable captures ok
}

NotificationCenter:

NotificationCenter.default.addUnsafeObserver(forName: noteName, object: nil, queue: nil) {
    // no Sendable requirements here
}

// this will assert if notification is delievered off the MainActor at runtime
NotificationCenter.default.addMainActorObserver(forName: noteName, object: nil) { notification in
    // statically MainActor will full access to Notification object
}

RunLoop additions:

// Guarantee that the current RunLoop turns at least once
await RunLoop.turn()

You can use ThreadExecutor to implement an actor that runs all of its methods on a dedicated thread with a functional runloop. This is conceptually similar to how the MainActor works.

Useful if you want to interate with RunLoop-based APIs. You can also use this to reduce the deadlock risk of synchronously blocking actor execution. Just be aware that both creating and switching to ThreadExecutor actors can be much more expensive than traditional actors.

actor ThreadActor {
    private let executor = ThreadExecutor(name: "my thread")

    nonisolated var unownedExecutor: UnownedSerialExecutor {
        executor.asUnownedSerialExecutor()
    }

    func runsOnThread() {
        // This will always execute on the same thread
    }
}

Installation

dependencies: [
    .package(url: "https://github.com/mattmassicotte/MainOffender", from: "0.1.0")
]

Contributing and Collaboration

I'd love to hear from you! Get in touch via mastodon, an issue, or a pull request.

I prefer collaboration, and would love to find ways to work together if you have a similar project.

I prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace.

By participating in this project you agree to abide by the Contributor Code of Conduct.

About

Utilities for transitioning to Swift Concurrency

Resources

License

Code of conduct

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages