In practice, many library APIs, like URLSession, will offload work to the background for you. We still have not introduced concurrency into our own code, because we didn't need to.
As a user of the library how would I know if the function uses @concurrent or nonisolated or @MainActor async function?
I can't seem to spot it based on the signature of the function
Should I rely on documentation or is there a way to detect this if implementation is not available?
Is this because the library may not have been migrated to Swift 6.2 and will be visible in future?
Reason for asking is I feel in some cases I need not create @concurrent function if I am simply using a async function (from the library) which does work in the background thread already.
For the sake of keeping your UI responsive it doesn't actually matter all that much. URLSession.data(from: url) may happen to use a background thread internally, but even if it didn't… it's really not doing all that much work. It sends off a network request, and then asks the system to let it know when data has arrived.
Until that data arrives, the function (and the Task containing it) is put in a suspended state, and set off to the side until the system wakes it up again. In the meantime, the thread was running that code is freed up to run other operations. The main thread is not blocked here.
When the data arrives, the system will wake the task up, put it back onto the appropriate thread, and let it process the data.
If data(from: url) were declared @MainActor, then the work of processing gathering the incoming network bytes into a Data object would happen on the main thread. That's not a lot of work, but it would be a small amount of work that would happen on the main thread. But that's the only work that would be happening on the main thread - even if this function were declared @MainActor, your main thread would not be blocked for the duration of the network call - it would only be blocked when that code is actively running.
I believe that is correct. I haven't seen any @concurrent annotations in the OS 26 SDKs, so I believe they're still using the previous behavior, which is that any async function without an explicit isolation behaves like @concurrent - it does all its synchronous work on a background thread. But I agree that it is a little hard to determine that right now, since the source code doesn't show it and the documentation doesn't show it.
Ultimately, the only way to be sure that your code performs well is to try it (ideally using tools like Instruments to measure it in detail). Documentation can help, but there are so many ways for code to be slow, and asynchrony only helps with some of them. For example:
It could be async, but internally take a mutex that code on the main thread also needs
It could cause cache contention on the reference count (or any other shared mutable state) of objects shared with the main thread
It could allocate tons of memory
It could do expensive preparatory work before or after the async part
Creating the thread to run the async work might be more time consuming than the actual work
It could async back to main actor/thread/queue internally
Moving the data from the async call back to the main thread could be time consuming
In general my recommendation is to start as simple as possible, then measure using the tools to locate and characterize performance problems. Once you've identified where to look, you can optimize the slow code, run it asynchronously, or both. Even better, sometimes you can just delete whatever it is
Thanks a lot @bjhomer for that detailed explanation.
You are correct would be suspended and wouldn't block anything.
That was an example (just to have a func signature), but something like processing of a photo from a 3rd party library might be using non isolated run on caller's executor but as a user might assume that it runs in the background and might call it from the main actor which could be a problem.
Yes async function would suspend and resume but still that processing work has to happen on some thread (main / background) so if it is on the main thread could hamper the UI. So as a user of library would be nice to know if it is concurrent or non isolated
The following is very helpful, thank you!!:
I believe that is correct. I haven't seen any @concurrent annotations in the OS 26 SDKs, so I believe they're still using the previous behavior, which is that any async function without an explicit isolation behaves like @concurrent - it does all its synchronous work on a background thread.
So once a library has been migrated to Swift 6.2, the function signature would be transparent to make it visible if it is annotated with concurrent / nonisolated / @MainActor`?
I agree it is better to have things as simple with the main thread and use async / concurrent functions as when needed. The WWDC video also recommends the same approach.
While Instruments is great would be a bit of work to debug using them every time a library's async function is used.
As long as once libraries are migrated to Swift 6.2 the functions signatures would contain concurrent / nonisolated / @MainActor that would be very helpful. I am assuming that is the case, correct me if I am wrong.
Perhaps. Something like network IO will likely be using different mechanisms than @concurrent under the hood though, since it's not computation. It'll be calling out to the OS's underlying asynchronous networking system, which isn't necessarily[1] even written in Swift.
It might be interesting to expose more things like that in the API declaration; there's been research languages that did things along those lines. My understanding is that it's trickier than it sounds to make it usable though.
I don't actually remember if it is, the point is just that it doesn't have to be ↩︎