@mattiem and @cocoaphony I'm curious what ya'll think about this. So my teammates and I were hoping that by Xcode 16 RC there would be better support for concurrency when working with CoreData. That hasn't happened, and doesn't feel like it's ever going to happen. Problem: we want to be ready to opt into Swift 6, and we want to avoid having to jump through hoops to quell warnings and errors. But then we run into stuff like this all over the place: (1 of N)
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@mattiem @cocoaphony The error disappears with (among other possible workarounds) prefixing the CoreData import with @-preconcurrency, however that feels like a cop-out. What's your gut take here: are we (Apple platform devs generally) facing a medium-term future where there are lots of valid "correct” API usages that, because they cannot be checked as valid and correct by the Swift concurrency checker, will permanently require manually suppressing and working around these warnings and errors?
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@mattiem @cocoaphony It would be lovely and helpful if Apple could publish some kind of Concurrency Roadmap that provided guidance about their intentions across their own frameworks, especially CoreData, so we knew what idiomatic code is supposed to look like now cc @holly — Even just something like “Swift Concurrency will never be able to compile valid Core Data usages without warnings/errors.” so that we know how to proceed with confidence.
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@jaredsinclair @cocoaphony @holly Ok, so at this point I feel like I need to learn how Core Data works because I get so many questions about it. I also think you are correct to be wary of @preconcurrency
. That expresses truth but not necessarily correctness.
But!
I can also fully answer your question! Because your question isn't about Core Data, at all! It is about isolation and Sendable types. And here's how I solved this problem, with some annotations.
=> More informations about this toot | More toots from mattiem@mastodon.social
@jaredsinclair @cocoaphony @holly Core Data isn't special. Lots of APIs are never going to be updated. But these kinds of APIs are the ones that put a particularly high burden on users, because they can require quite a deep understanding of the language. It is often possible to find workarounds. Sometimes these come with big trade-offs though. You cannot always have the isolation/design you want. Things out of your control can get in the way, like this your example here.
=> More informations about this toot | More toots from mattiem@mastodon.social
@mattiem The relevant bit of info on CoreData re: lowercase-c concurrency is this: the API surface of NSManagedObjectContext has a mixture of methods and properties that are either (A) safe to access from anywhere or (B) are only safe to access from within block methods isolated to the context’s queue (either the main queue or a private serial queue). Validating for correct usage requires a combination of peer review (obviously) and runtime checks with a launch argument. @cocoaphony @holly
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@jaredsinclair @cocoaphony @holly yeah this is a tough situation. It's very reminiscent of NSDocument (which also has a custom lowercase c concurrency system). I bet that you can get quite far with dynamic isolation (ie MainActor.assumeIsolated), but I don't have enough experience with Core Data to say this with confidence.
=> More informations about this toot | More toots from mattiem@mastodon.social
@mattiem Yep, one of several workarounds. My question is more at the meta level: what does best practice look like now that Xcode 16 is in RC when working with first party or vendor libraries that aren’t updated? Part of me thinks @-preconcurrency is the most honest default here because it’s naming the actual problem. @cocoaphony @holly
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@jaredsinclair @cocoaphony @holly so in this case, preconcurrency would have resulted in a race I'm pretty sure.
I think the best pratice is probably to not use preconcurrency import, and either find suitable work arounds or not use concurrency at all if that's too challenging. But, I don't know if that's reasonable...
=> More informations about this toot | More toots from mattiem@mastodon.social
@mattiem Actually in this case it would not have resulted in a race (setting aside deadlock issues with GCD which I didn't call out explicitly, because Mastodon tootspace is small and I wanted a single sloppy screenshot). It’s valid usage to pass the context around so long as you use perform…
method blocks to constrain accesses of the context (constraining the kinds of accesses that do result in races when not called from within said perform…
method blocks: insert/delete/save).
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@jaredsinclair ahhh right ok i understand! That does make sense. Yeah, hard-to-use-with-concurrency API for sure. Actually quite similar to NSDocument, which also uses a custom non-GCD-based lowercase-c concurrency system.
Does the context use a GCD queue you can get access to and/or control? It might be possible to explore some executor trickiness. But its so hard for me to say, because I don't fully understand the problems you are encountering...
=> More informations about this toot | More toots from mattiem@mastodon.social
@mattiem A bit o’ CoreData history might help: this valid, idiomatic usage of NSManagedObjectContext that I've been describing was, like, really hard-won. There was a time, before perform(block)
methods were added, when you, the consuming developer, had to manage your own thread confinement for your contexts. It was fraught with peril, hence the introduction of built-in thread confinement. The queues are hidden from the caller, but the caller does get to configure the concurrencyType
.
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@mattiem There are two concurrency types defined: main queue and private queue, which are exactly what you'd think. So if you already happen to know that you're on the main queue, and your context is a "view context", i.e. it uses the main queue concurrency type, I it is safe to directly access the race-prone members without wrapping in perform(block)
. If you don't know, or if it's definitely a private queue concurrency type, then idiomatic usage dictates constraining access to perform(block).
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@mattiem It is difficult to imagine how the Swift concurrency checker could possibly validate this usage, not without the introduction of some first-party Swift-native overlay of new types on top of NSManagedObjectContext (subclasses or wrapper classes) that make the main-actor isolation or nonisolation explicit and final.
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@jaredsinclair I was kinda aware of this, and definitely recall it being a thing.
This kind of stuff is exactly what dynamic isolation is for. You know what isolation is in effect (say, MainActor or some queue), but it is invisible to the compiler. It is not perfect, but has that been helpful at all?
Alternatively, it might be possible to build such a wrapper that encodes the static isolation and then internally expresses it dynamically...
=> More informations about this toot | More toots from mattiem@mastodon.social
@mattiem My intuition here is that two distinct classes would be easier to maintain, and easier for new teammates to reason about: a main actor version that is deliberately not Sendable and meant for user-visible interactions and data, and a Sendable version that makes non-idiomatic usage hard/impossible and takes whatever precautions/workarounds are necessary to preserve Swift concurrency checks (adding a bunch of methods that shadow the wrapped
methods and only allow for safe access).
=> More informations about this toot | More toots from jaredsinclair@mastodon.social
@jaredsinclair you could be right. It's hard for me to say, because I don't understand the APIs or how they need to be used. But, a wrapper does seem very reasonable to me and is almost certainly where I would start if I were tasked with this.
=> More informations about this toot | More toots from mattiem@mastodon.social
@jaredsinclair @mattiem @cocoaphony Grand Central Dispatch will be your friend for years to come.
=> More informations about this toot | More toots from danielinoa@mastodon.social
@danielinoa @jaredsinclair @cocoaphony yeah this will definitely work if you don't want to move to concurrency.
=> More informations about this toot | More toots from mattiem@mastodon.social This content has been proxied by September (ba2dc).Proxy Information
text/gemini