Concurrency

RSS for tag

Concurrency is the notion of multiple things happening at the same time.

Posts under Concurrency tag

84 Posts

Post

Replies

Boosts

Views

Activity

Modelactors, Repository and bootloader
In iOS 26, should we have bootloader that runs the repo on startup - or should we have that inside tasks in root view? we have repos that runs as a «closed» functions, we dont throw but updates swiftdata and we use @query in the views. So what is best? and for the repo we should have a repo that runs the upserts manage relations eg? Should that run on a modelactor?
0
0
222
Sep ’25
Waiting for an Async Result in a Synchronous Function
This comes up over and over, here on the forums and elsewhere, so I thought I’d post my take on it. If you have questions or comments, start a new thread here on the forums. Put it in the App & System Services > Processes & Concurrency subtopic and tag it with Concurrency. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Waiting for an Async Result in a Synchronous Function On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function. Lemme say that again, with emphasis… On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function. This post dives into the details of this reality. Prime Offender Imagine you have an asynchronous function and you want to call it from a synchronous function: func someAsynchronous(input: Int, completionHandler: @escaping @Sendable (_ output: Int) -> Void) { … processes `input` asynchronously … … when its done, calls the completion handler with the result … } func mySynchronous(input: Int) -> Int { … calls `someAsynchronous(…)` … … waits for it to finish … … results the result … } There’s no good way to achieve this goal on Apple platforms. Every approach you might try has fundamental problems. A common approach is to do this working using a Dispatch semaphore: func mySynchronous(input: Int) -> Int { fatalError("DO NOT WRITE CODE LIKE THIS") let sem = DispatchSemaphore(value: 0) var result: Int? = nil someAsynchronous(input: input) { output in result = output sem.signal() } sem.wait() return result! } Note This code produces a warning in the Swift 5 language mode which turns into an error in the Swift 6 language mode. You can suppress that warning with, say, a Mutex. I didn’t do that here because I’m focused on a more fundamental issue here. This code works, up to a point. But it has unavoidable problems, ones that don’t show up in a basic test but can show up in the real world. The two biggest ones are: Priority inversion Thread pools I’ll cover each in turn. Priority Inversion Apple platforms have a mechanism that helps to prevent priority inversion by boosting the priority of a thread if it holds a resource that’s needed by a higher-priority thread. The code above defeats that mechanism because there’s no way for the system to know that the threads running the work started by someAsynchronous(…) are being waited on by the thread blocked in mySynchronous(…). So if that blocked thread has a high-priority, the system can’t boost the priority of the threads doing the work. This problem usually manifests in your app failing to meet real-time goals. An obvious example of this is scrolling. If you call mySynchronous(…) from the main thread, it might end up waiting longer than it should, resulting in noticeable hitches in the scrolling. Threads Pools A synchronous function, like mySynchronous(…) in the example above, can be called by any thread. If the thread is part of a thread pool, it consumes a valuable resource — that is, a thread from the pool — for a long period of time. The raises the possibility of thread exhaustion, that is, where the pool runs out of threads. There are two common thread pools on Apple platforms: Dispatch Swift concurrency These respond to this issue in different ways, both of which can cause you problems. Dispatch can choose to over-commit, that is, start a new worker thread to get work done while you’re hogging its existing worker threads. This causes two problems: It can lead to thread explosion, where Dispatch starts dozens and dozens of threads, which all end up blocked. This is a huge waste of resources, notably memory. Dispatch has an hard limit to how many worker threads it will create. If you cause it to over-commit too much, you’ll eventually hit that limit, putting you in the thread exhaustion state. In contrast, Swift concurrency’s thread pool doesn’t over-commit. It typically has one thread per CPU core. If you block one of those threads in code like mySynchronous(…), you limit its ability to get work done. If you do it too much, you end up in the thread exhaustion state. WARNING Thread exhaustion may seem like just a performance problem, but that’s not the case. It’s possible for thread exhaustion to lead to a deadlock, which blocks all thread pool work in your process forever. There’s a trade-off here. Swift concurrency doesn’t over-commit, so it can’t suffer from thread explosion but is more likely deadlock, and vice versa for Dispatch. Bargaining Code like the mySynchronous(…) function shown above is fundamentally problematic. I hope that the above has got you past the denial stage of this analysis. Now let’s discuss your bargaining options (-: Most folks don’t set out to write code like mySynchronous(…). Rather, they’re working on an existing codebase and they get to a point where they have to synchronously wait for an asynchronous result. At that point they have the choice of writing code like this or doing a major refactor. For example, imagine you’re calling mySynchronous(…) from the main thread in order to update a view. You could go down the problematic path, or you could refactor your code so that: The current value is always available to the main thread. The asynchronous code updates that value in an observable way. The main thread code responds to that notification by updating the view from the current value. This refactoring may or may not be feasible given your product’s current architecture and timeline. And if that’s the case, you might end up deploying code like mySynchronous(…). All engineering is about trade-offs. However, don’t fool yourself into thinking that this code is correct. Rather, make a note to revisit this choice in the future. Async to Async Finally, I want to clarify that the above is about synchronous functions. If you have a Swift async function, there is a good path forward. For example: func mySwiftAsync(input: Int) async -> Int { let result = await withCheckedContinuation { continuation in someAsynchronous(input: input) { output in continuation.resume(returning: output) } } return result } This looks like it’s blocking the current thread waiting for the result, but that’s not what happens under the covers. Rather, the Swift concurrency worker thread that calls mySwiftAsync(…) will return to the thread pool at the await. Later, when someAsynchronous(…) calls the completion handler and you resume the continuation, Swift will grab a worker thread from the pool to continue running mySwiftAsync(…). This is absolutely normal and doesn’t cause the sorts of problems you see with mySynchronous(…). IMPORTANT To keep things simple I didn’t implement cancellation in mySwiftAsync(…). In a real product it’s important to support cancellation in code like this. See the withTaskCancellationHandler(operation:onCancel:isolation:) function for the details.
0
0
924
Oct ’25
DeviceCheck Framework Crash: DCAnalytics nil Dictionary Insertion in Production
We're experiencing crashes in our production iOS app related to Apple's DeviceCheck framework. The crash occurs in DCAnalytics internal performance tracking, affecting some specific versions of iOS 18 (18.4.1, 18.5.0). Crash Signature CoreFoundation: -[__NSDictionaryM setObject:forKeyedSubscript:] + 460 DeviceCheck: -[DCAnalytics sendPerformanceForCategory:eventType:] + 236 Observed Patterns Scenario 1 - Token Generation: Crashed: com.appQueue EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000010 DeviceCheck: -[DCDevice generateTokenWithCompletionHandler:] Thread: Background dispatch queue Scenario 2 - Support Check: Crashed: com.apple.main-thread EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000008 DeviceCheck: -[DCDevice _isSupportedReturningError:] DeviceCheck: -[DCDevice isSupported] Thread: Main thread Root Cause Analysis The DCAnalytics component within DeviceCheck attempts to insert a nil value into an NSMutableDictionary when recording performance metrics, indicating missing nil validation before dictionary operations. Reproduction Context Crashes occur during standard DeviceCheck API usage: Calling DCDevice.isSupported property Calling DCDevice.generateToken(completionHandler:) (triggered by Firebase App Check SDK) Both operations invoke internal analytics that fail with nil insertion attempts. Concurrency Considerations We've implemented sequential access guards around DeviceCheck token generation to prevent race conditions, yet crashes persist. This suggests the issue likely originates within the DeviceCheck framework's internal implementation rather than concurrent access from our application code. Note: Scenario 2 occurs through Firebase SDK's App Check integration, which internally uses DeviceCheck for attestation. Request Can Apple engineering confirm if this is a known issue with DeviceCheck's analytics subsystem? Is there a recommended workaround to disable DCAnalytics or ensure thread-safe DeviceCheck API usage? Any guidance on preventing these crashes would be appreciated.
0
2
212
Nov ’25
Task cancellation behavior
Hi everyone, I believe this should be a simple and expected default behavior in a real-world app, but I’m unable to make it work: I have a View (a screen/page in this case) that calls an endpoint using async/await. If the endpoint hasn’t finished, but I navigate forward to a DetailView, I want the endpoint to continue fetching data (i.e., inside the @StateObject ViewModel that the View owns). This way, when I go back, the View will have refreshed with the fetched data once it completes. If the endpoint hasn’t finished and I navigate back to the previous screen, I want it to be canceled, and the @StateObject ViewModel should be deinitialized. I can achieve 1 and 3 using the .task modifier, since it automatically cancels the asynchronous task when the view disappears: view .task { await vm.getData() } I can achieve 1 and 2 using a structured Task in the View (or in the ViewModel, its the same behavior), for example: .onFirstAppearOnly { Task { away vm.getData() } } onFirstAppearOnly is a custom modifier that I have for calling onAppear only once in view lifecycle. Just to clarify, I dont think that part is important for the purpose of the example My question is: How can I achieve all three behaviors? My minimum deployment target is iOS 15, and I’m using NavigationView + NavigationLink. However, I have also tried using NavigationStack + NavigationPath and still couldn’t get it to work. Any help would be much appreciated. Thank you!
0
0
193
Jan ’26
Modelactors, Repository and bootloader
In iOS 26, should we have bootloader that runs the repo on startup - or should we have that inside tasks in root view? we have repos that runs as a «closed» functions, we dont throw but updates swiftdata and we use @query in the views. So what is best? and for the repo we should have a repo that runs the upserts manage relations eg? Should that run on a modelactor?
Replies
0
Boosts
0
Views
222
Activity
Sep ’25
Waiting for an Async Result in a Synchronous Function
This comes up over and over, here on the forums and elsewhere, so I thought I’d post my take on it. If you have questions or comments, start a new thread here on the forums. Put it in the App & System Services > Processes & Concurrency subtopic and tag it with Concurrency. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Waiting for an Async Result in a Synchronous Function On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function. Lemme say that again, with emphasis… On Apple platforms there is no good way for a synchronous function to wait on the result of an asynchronous function. This post dives into the details of this reality. Prime Offender Imagine you have an asynchronous function and you want to call it from a synchronous function: func someAsynchronous(input: Int, completionHandler: @escaping @Sendable (_ output: Int) -> Void) { … processes `input` asynchronously … … when its done, calls the completion handler with the result … } func mySynchronous(input: Int) -> Int { … calls `someAsynchronous(…)` … … waits for it to finish … … results the result … } There’s no good way to achieve this goal on Apple platforms. Every approach you might try has fundamental problems. A common approach is to do this working using a Dispatch semaphore: func mySynchronous(input: Int) -> Int { fatalError("DO NOT WRITE CODE LIKE THIS") let sem = DispatchSemaphore(value: 0) var result: Int? = nil someAsynchronous(input: input) { output in result = output sem.signal() } sem.wait() return result! } Note This code produces a warning in the Swift 5 language mode which turns into an error in the Swift 6 language mode. You can suppress that warning with, say, a Mutex. I didn’t do that here because I’m focused on a more fundamental issue here. This code works, up to a point. But it has unavoidable problems, ones that don’t show up in a basic test but can show up in the real world. The two biggest ones are: Priority inversion Thread pools I’ll cover each in turn. Priority Inversion Apple platforms have a mechanism that helps to prevent priority inversion by boosting the priority of a thread if it holds a resource that’s needed by a higher-priority thread. The code above defeats that mechanism because there’s no way for the system to know that the threads running the work started by someAsynchronous(…) are being waited on by the thread blocked in mySynchronous(…). So if that blocked thread has a high-priority, the system can’t boost the priority of the threads doing the work. This problem usually manifests in your app failing to meet real-time goals. An obvious example of this is scrolling. If you call mySynchronous(…) from the main thread, it might end up waiting longer than it should, resulting in noticeable hitches in the scrolling. Threads Pools A synchronous function, like mySynchronous(…) in the example above, can be called by any thread. If the thread is part of a thread pool, it consumes a valuable resource — that is, a thread from the pool — for a long period of time. The raises the possibility of thread exhaustion, that is, where the pool runs out of threads. There are two common thread pools on Apple platforms: Dispatch Swift concurrency These respond to this issue in different ways, both of which can cause you problems. Dispatch can choose to over-commit, that is, start a new worker thread to get work done while you’re hogging its existing worker threads. This causes two problems: It can lead to thread explosion, where Dispatch starts dozens and dozens of threads, which all end up blocked. This is a huge waste of resources, notably memory. Dispatch has an hard limit to how many worker threads it will create. If you cause it to over-commit too much, you’ll eventually hit that limit, putting you in the thread exhaustion state. In contrast, Swift concurrency’s thread pool doesn’t over-commit. It typically has one thread per CPU core. If you block one of those threads in code like mySynchronous(…), you limit its ability to get work done. If you do it too much, you end up in the thread exhaustion state. WARNING Thread exhaustion may seem like just a performance problem, but that’s not the case. It’s possible for thread exhaustion to lead to a deadlock, which blocks all thread pool work in your process forever. There’s a trade-off here. Swift concurrency doesn’t over-commit, so it can’t suffer from thread explosion but is more likely deadlock, and vice versa for Dispatch. Bargaining Code like the mySynchronous(…) function shown above is fundamentally problematic. I hope that the above has got you past the denial stage of this analysis. Now let’s discuss your bargaining options (-: Most folks don’t set out to write code like mySynchronous(…). Rather, they’re working on an existing codebase and they get to a point where they have to synchronously wait for an asynchronous result. At that point they have the choice of writing code like this or doing a major refactor. For example, imagine you’re calling mySynchronous(…) from the main thread in order to update a view. You could go down the problematic path, or you could refactor your code so that: The current value is always available to the main thread. The asynchronous code updates that value in an observable way. The main thread code responds to that notification by updating the view from the current value. This refactoring may or may not be feasible given your product’s current architecture and timeline. And if that’s the case, you might end up deploying code like mySynchronous(…). All engineering is about trade-offs. However, don’t fool yourself into thinking that this code is correct. Rather, make a note to revisit this choice in the future. Async to Async Finally, I want to clarify that the above is about synchronous functions. If you have a Swift async function, there is a good path forward. For example: func mySwiftAsync(input: Int) async -> Int { let result = await withCheckedContinuation { continuation in someAsynchronous(input: input) { output in continuation.resume(returning: output) } } return result } This looks like it’s blocking the current thread waiting for the result, but that’s not what happens under the covers. Rather, the Swift concurrency worker thread that calls mySwiftAsync(…) will return to the thread pool at the await. Later, when someAsynchronous(…) calls the completion handler and you resume the continuation, Swift will grab a worker thread from the pool to continue running mySwiftAsync(…). This is absolutely normal and doesn’t cause the sorts of problems you see with mySynchronous(…). IMPORTANT To keep things simple I didn’t implement cancellation in mySwiftAsync(…). In a real product it’s important to support cancellation in code like this. See the withTaskCancellationHandler(operation:onCancel:isolation:) function for the details.
Replies
0
Boosts
0
Views
924
Activity
Oct ’25
DeviceCheck Framework Crash: DCAnalytics nil Dictionary Insertion in Production
We're experiencing crashes in our production iOS app related to Apple's DeviceCheck framework. The crash occurs in DCAnalytics internal performance tracking, affecting some specific versions of iOS 18 (18.4.1, 18.5.0). Crash Signature CoreFoundation: -[__NSDictionaryM setObject:forKeyedSubscript:] + 460 DeviceCheck: -[DCAnalytics sendPerformanceForCategory:eventType:] + 236 Observed Patterns Scenario 1 - Token Generation: Crashed: com.appQueue EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000010 DeviceCheck: -[DCDevice generateTokenWithCompletionHandler:] Thread: Background dispatch queue Scenario 2 - Support Check: Crashed: com.apple.main-thread EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000008 DeviceCheck: -[DCDevice _isSupportedReturningError:] DeviceCheck: -[DCDevice isSupported] Thread: Main thread Root Cause Analysis The DCAnalytics component within DeviceCheck attempts to insert a nil value into an NSMutableDictionary when recording performance metrics, indicating missing nil validation before dictionary operations. Reproduction Context Crashes occur during standard DeviceCheck API usage: Calling DCDevice.isSupported property Calling DCDevice.generateToken(completionHandler:) (triggered by Firebase App Check SDK) Both operations invoke internal analytics that fail with nil insertion attempts. Concurrency Considerations We've implemented sequential access guards around DeviceCheck token generation to prevent race conditions, yet crashes persist. This suggests the issue likely originates within the DeviceCheck framework's internal implementation rather than concurrent access from our application code. Note: Scenario 2 occurs through Firebase SDK's App Check integration, which internally uses DeviceCheck for attestation. Request Can Apple engineering confirm if this is a known issue with DeviceCheck's analytics subsystem? Is there a recommended workaround to disable DCAnalytics or ensure thread-safe DeviceCheck API usage? Any guidance on preventing these crashes would be appreciated.
Replies
0
Boosts
2
Views
212
Activity
Nov ’25
Task cancellation behavior
Hi everyone, I believe this should be a simple and expected default behavior in a real-world app, but I’m unable to make it work: I have a View (a screen/page in this case) that calls an endpoint using async/await. If the endpoint hasn’t finished, but I navigate forward to a DetailView, I want the endpoint to continue fetching data (i.e., inside the @StateObject ViewModel that the View owns). This way, when I go back, the View will have refreshed with the fetched data once it completes. If the endpoint hasn’t finished and I navigate back to the previous screen, I want it to be canceled, and the @StateObject ViewModel should be deinitialized. I can achieve 1 and 3 using the .task modifier, since it automatically cancels the asynchronous task when the view disappears: view .task { await vm.getData() } I can achieve 1 and 2 using a structured Task in the View (or in the ViewModel, its the same behavior), for example: .onFirstAppearOnly { Task { away vm.getData() } } onFirstAppearOnly is a custom modifier that I have for calling onAppear only once in view lifecycle. Just to clarify, I dont think that part is important for the purpose of the example My question is: How can I achieve all three behaviors? My minimum deployment target is iOS 15, and I’m using NavigationView + NavigationLink. However, I have also tried using NavigationStack + NavigationPath and still couldn’t get it to work. Any help would be much appreciated. Thank you!
Replies
0
Boosts
0
Views
193
Activity
Jan ’26