-1

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?

daniel
  • 1,446
  • 3
  • 29
  • 65
  • 2
    `It seems almost a certainty by definition that an asynchronous code runs on a different thread than the thread it was called from.`: `DispatchQueue.main.async { DispatchQueue.main.async { print("Hello") } print("World") }` Is that ran on different threads? Is it async? The answer is no, yes. It is indeed async, and ran on the exact same thread. The same thread is executing the items queued up for execution. – Brandon Dec 01 '21 at 23:15
  • @Brandon Yes. That makes sense. I haven't thought of that scenario. That does help me understand the concept of asynchronous in iOS and Swift better, but I still don't know if any code I use that modifies the layout engine can be run in a completion handler marked as escaping. – daniel Dec 01 '21 at 23:23
  • 2
    `MyQueue.async { print("a") }`, `MyQueue.async { print("b") }` Are these ran on the same thread? `MAYBE`. If `MyQueue` has multiple threads, the items MAY run on separate threads internally, but we don't care as long as it is async. If the queue has ONE thread, the items will run on the same thread internally.. but again, we don't really care. The system decides. The only `DispatchQueue` that guarantees running on the same thread, is `main` queue. Any code modifying the UI needs to be done on the main queue only. The `escaping` keyword doesn't signify what thread to run on. – Brandon Dec 01 '21 at 23:24
  • 2
    `escaping` literally means that the callback might exist somewhere else in the code and that it may or may not `out-live` the current thread. The idea is just that it `might`, so you need to make sure any variables used inside the closure/callback are still alive when the callback is invoked. If a callback/closure if NOT marked `escaping`, it means the callback is usually an inline closure or runs on the same thread, or does not out-live the scope of the function that is using it. That's it. The `escaping` closure can be ran anytime, and anywhere. The non-escaping closure will be ran instantly – Brandon Dec 01 '21 at 23:28
  • @Brandon I think you may be guiding me to the conclusion that I should put any code that modifies the layout engine within DispatchQueue.main.async { } if that code is run in a completion handler marked as escaping. I could do that and hope that fixes my problem, but I wouldn't know for sure if that is the actual cause of the problem. I really would like to know if the escaping keyword causes a completion handler closure to un on a different thread or the same thread as it is called from. – daniel Dec 01 '21 at 23:29

1 Answers1

3

@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.

Brandon
  • 22,723
  • 11
  • 93
  • 186
  • Ok. That makes sense. Thank you. That has really helped. It's essential to know that about the @escaping keyword. Thanks again. – daniel Dec 01 '21 at 23:50