I really think that if code in Swift in iOS runs asynchronously, then it is by the nature of itself that it runs on a separate thread than the thread the code is called from - usually the main thread. It seems obvious to me, but I am uncertain and would like to receive verification of this. I can't think of any conditions in which an asynchronous code would run on the same code that it was called from. It seems almost a certainty by definition that an asynchronous code runs on a different thread than the thread it was called from.
What do you think? Would you agree and verify this?
I ask this question while trying to understand the @escaping keyword as it applies to completion handlers. The Swift documentation on Closuressays that the @escaping keyword causes the completion handler that is designated as escaping to run asynchronously and to run after the function (that receives the completion closure as an argument) finishes running. The documentation, however, does not say whether the escaping completion handler runs on a different thread or on the current thread - which would be the main/UI thread.
I'm trying to hunt where a run-time error is coming from. The error message says: "Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread."
It might be coming from the completion handler of CNContactStore requestAccess(for:completionHandler:), but I need to know if @escaping causes the closure it applies to to run on a different thread than the thread the closure was called from, since the completionHandler parameter is defined as escaping by the @escaping keyword in the definition of CNContactStore requestAccess(for:completionHandler:).
Is the error I'm getting coming from code inside the completion handler of that function that modifies the layout engine or any completion handler marked as escaping?
These posts on stackoverflow helped me clarify my question, but they do not answer my question:
Does Asynchronous code run in the UI thread or a new/different thread to not block the UI?
async - stay on the current thread?
CodePudding user response:
@escaping
does NOT determine which thread the closure is ran on. The documentation does.
For that specific function (https://developer.apple.com/documentation/contacts/cncontactstore/1402873-requestaccess/):
The system executes completionHandler on an arbitrary queue. It is recommended that you use CNContactStore instance methods in this completion handler instead of the UI main thread.
If you are doing any sort of UI work inside of the completionHandler
callback, it means you MUST call DispatchQueue.main.async { update_my_UI_here() }
to execute your code safely on the main thread.
Example:
requestAccess(for: .contacts) { [weak self] permissionGranted, error in
//All code in here is ran on an ARBITRARY background queue
if let error = error {
log(error)
return
}
//CNContactStore - Any CNContactStore functions should run here, and not inside `DispatchQueue.main`
guard let self = self else { return }
// Any UI updates or any code that interacts with `UI*` or constraints, must be done on main
DispatchQueue.main.async {
self.update_my_ui_here(permisionGranted) //Safely update UI from the main queue
}
}
Marking a function as @escaping
just means that it may POTENTIALLY be called at a later time or stored as a variable somewhere. That's all it means. It does NOT mean that is will be ran on the same OR a different thread. It doesn't have any guarantees about threads and it doesn't have any guarantees about when it will run.
After all, DispatchQueue.main.async(completion: @escaping () -> Void)
has an escaping parameter, and yet it always runs the completion
on the exact same main
thread. main
is the only queue with such guarantee, regardless of whether the parameter is escaping or not.