Making just one type @MainActor can result in cascade of errors at all usage sites where the compiler now cannot provide that MainActor guarantee. This virality can make it really hard to incrementally adopt concurrency with targeted changes. Perhaps that’s not too big a deal for smaller code bases/teams, but I bet this is a killer for big projects. So what do you do?
You make use of dynamic isolation to contain the spread!
Instead of throwing (static) type annotations around, you can ease into the adoption of actor isolation with (dynamic) preconditions and running blocks of structured and unscructured concurrent code.
Ok ok, I know Super Saturday Sharefest is not a thing, but it’s too late for Follower Friday, alright? I want to give a big shout-out to Matt Massicotte of ChimeHQ for dropping another awesome Swift open source package that makes using XPC Swift-ier. I’m really grateful for all the amazing work Matt has been doing and for sharing it with the community.
Here is a transaction type to copy & paste into projects to encapsulates thread-safe read/write access: Just make sure to not use the main queue, because .sync call from main to main will deadlock your app! It ensures you read values synchronously, which isn’t dangerous, and enqueue and execute write operations in order. This is useful if you need to access any resource from multiple threads and want to avoid the overhead of mutex locks.
Today I wrote my first asynchronous test in my latest project and suddenly, during the waitForExpectations run loop, a totally unrelated test’s stuff crashed. To make things worse, it was part of an RxSwift subscription. No idea who kept that alive and where the EXC_BAD_ACCESS really originated.
To debug my threading issues and help bring forth future problems, I have created a simmple object that slows the current queue down:
letIsRunningTests=NSClassFromString("XCTestCase")!=nilstructQueueJigglePoint{/// Randomly interfere with the thread.staticfuncjiggle(){guard!IsRunningTestselse{return}#if DEBUGusleep(2*1000000)// 2 seconds#endif}}
I got this idea from Brett Schuchert on pages 188–90 of Uncle Bob’s Clean Code. A Handbook of Agile Software Craftsmanship. There, interference with traditional threading should randomly sleep, yield, or fall through. Enqueued blocks are a lot less volatile, so I only came up with sleeping.
Randomizing the sleep interval is up next. But a fixed number of 2–10 seconds helps find UI-blocking code already.
Just throw in a QueueJigglePoint.jiggle() in NSManagedObjectContext.performBlock executions, when dispatching async to the background, or when reading files, for example.
I ran into problems with my Core Data Unit of Work/Transaction implementation the other day. I was not exercising good NSManagedObjectContext hygiene and end up with conflicts from time to time. Race conditions. Hate ‘em. The problem is that the parent–child setup of Core Data managed object contexts seems to work just fine for so-called “scratch pad” contexts: create a new child context, perform changes on in, then discard, or save to pass changes to the parent context.
I’ve written about using the Result enum to return values or errors already. Result guards against errors in the process. There’s also a way to guard against latency, often called “Futures”. There exist well-crafted solutions in Swift already.