Understanding escaping closures Swift - ios

Need help with escaping closures.
I understand that the definition of escaping closures is If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is escaping.
I believe there are a few scenarios where escaping closures are necessary.
Setting an outside variable as the passing closure
I understand this because the variable lives even after the function returns.
Asynchronous operations
Things like Dispatch.main.async{} or Network Request.This is the part the I don't quite understand. I understand that Dispatch.main.async{} runs on a different thread and that async operations take a while to complete hence the name async. However, I cannot understand how these operations outlive the scope of the function.

When you call DispatchQueue.main.async{} you are creating a closure (a block in Objective-C) that is sent to the given dispatch queue. Your function will then continue running after the async call and could end before the block has a chance to run on the DispatchQueue. The closure has "escaped"!
The code in the block will actually be run when the block reaches the front of its DispatchQueue
The async doesn't mean that the operation will take a long time, it means that this function (the one calling async) will not pause to wait on the block to complete.
(if you DID want to pause and wait for the operation to complete, you could call sync instead of async).

When you do asynchronous work it might escape the current scope cause, by definition, it won't be waited for its completion.
This is also why the terminology completion is used for those closures adopted in asynchronous code for the delivery of results.
Here's an example, assuming queue is a DispatchQueue and someHugeWork() -> R is a synchronous function which returns a result R:
func doItSync() -> R {
var result: R!
queue.sync {
result = someHugeWork()
}
// the function has to wait for the code submitted synchronously
// on the queue.
return result
}
func doItAsync(completion: #escaping (R) -> Void) {
queue.async {
let result = someHugeWork()
completion(result)
}
// work was submitted asynchronously on the queue, hence the function
// just returns, therefore the completion closure might escape this
// scope.
}
Now, obviously in this case doItSync() does not really make sense, since it'll block the current thread until someHugeWork() has executed on a different queue. That is also why you can return the result from its scope: it waits for it. I've only added this to let you see the difference between synchronous code and asynchronous code for returning a result.
On the other hand doItAsync(completion:) just returns immediately without having to wait for someHugeWork() to complete on a different queue, hence it won't block the current thread.
Since you've submitted this work asynchronously on the queue you now have no control about when it will be executed and that is also why your only way to return the result from someHugeWork() is by using a completion closure, which has to be captured inside the work item submitted asynchronously… If you think about it for a moment it'll be as if such completion closure is stored to be retrieved by the queue at a later time when it will executes the work item submitted asynchronously (you said in your question that you did got the concept of storing a closure).
Now let's also see a method which doesn't need an escaping closure, that'll be the body parameter of Sequence's forEach(_:) method: this method executes the given closure on every element of the sequence, that'll be as in doing:
func forEach(_ body: (Element) -> Void) {
for element in self {
body(element)
}
// the function returns only after the for-in loop has completed,
// hence body closure only executes in the scope of this method,
// thus it never escapes.
}
(for the sake of simplicity I've omitted the throwing annotations)
In this case the body closure never escapes forEach(_:) method's scope, therefore it doesn't need to be annotated as #escaping: it will only executes inside the for-in loop which has to complete before the method can return.

Related

Is asynchronous code always run on a different thread?

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?
#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.

When would a queue consider a task is completed?

In the following code, when would queueT (serial queue) consider “task A” is completed?
The moment when aNetworkRequest switched to another thread?
Or in the doneInAnotherQueue block? ( commented // 1)
In another word, when would “task B” be executed?
let queueT = DispatchQueue(label: "com.test.a")
queueT.async { // task A
aNetworkRequest.doneInAnotherQueue() { // completed in another thread possibly
// 1
}
}
queueT.async { // task B
print("It's my turn")
}
It would much better if you could explain the mechanism how a queue consider a task is completed.
Thanks in advance.
In short, the first example starts an asynchronous network request, so the async call “finishes” as soon as that network request is submitted (but does not wait for that network request to finish).
I am assuming that the real question is that you want to know when the network request is done. Bottom line, GCD is not well suited for managing dependencies between tasks that are, themselves, asynchronous requests. The dispatching the initiation of a network request to a serial queue is undoubtedly not going to achieve what you want. (And before someone suggests using semaphores or dispatch groups to wait for the asynchronous request to finish, note that can solve the tactical issue, but it is a pattern to be avoided because it is inefficient use of resources and, in edge cases, can introduce deadlocks.)
One pattern is to use completion handlers:
func performRequestA(completion: #escaping () -> Void) { // task A
aNetworkRequest.doneInAnotherQueue() { object in
...
completion()
}
}
Now, in practice, we would generally use the completion handler with a parameter, perhaps even a Result type:
func performRequestA(completion: #escaping (Result<Foo, Error>) -> Void) { // task A
aNetworkRequest.doneInAnotherQueue() { result in
guard ... else {
completion(.failure(error))
return
}
let foo = ...
completion(.success(foo))
}
}
Then you can use the completion handler pattern, to process the results, update models, and perhaps initiate subsequent requests that are dependent upon the results of this request. For example:
performRequestA { result in
switch result {
case .failure(let error):
print(error)
case .success(let foo):
// update models or initiate next step in the process here
}
}
If you are really asking how to manage dependencies between asynchronous tasks, there are a number of other, elegant patterns (e.g., Combine, custom asynchronous Operation subclass, the forthcoming async/await pattern contemplated in SE-0296 and SE-0303, etc.). All of these are elegant solutions for managing dependencies between asynchronous tasks, controlling the degree of concurrency, etc.
We probably would need to better understand the nature of your broader needs before we made any specific recommendations. You have asked the question about a single dispatch, but the question probably is best viewed from a broader context of what you are trying to achieve. For example, I'm assuming you are asking because you have multiple asynchronous requests to initiate: Do you really need to make sure that they happen sequentially and lose all the performance benefits of concurrency? Or can you allow them to run concurrently and you just need to know when all of the concurrent requests are done and how to get the results in the correct order? And might you have so many concurrent requests that you might need to constrain the degree of concurrency?
The answers to those questions will probably influence our recommendation of how to best manage your multiple asynchronous requests. But the answer is almost certainly is not a GCD queue.
You can do a simple check
let queueT = DispatchQueue(label: "com.test.a")
queueT.async { // task A
DispatchQueue(label: "com.test2.a").async { // create another queue inside
for i in 0..<6 {
print(i)
}
}
}
queueT.async { // task B
for i in 10..<20 {
print(i)
}
}
}
you'll get different output each run this means yes when you switch thread the task is considered done
A GCD work item is complete when the closure you pass returns. So for your example, I'm going to rewrite it to make the function calls and parameters more explicit (rather than using trailing closure syntax).
queueT.async(execute: {
// This is a function call that takes a closure parameter. Whether this
// function returns, then this closure will continue. Whether that is before or
// after running completionHandler is an internal detail of doneInAnotherQueue.
aNetworkRequest.doneInAnotherQueue(closureParameter: { ... })
// At this point, the closure is complete. What doneInAnotherQueue() does with
// its closure is its business.
})
Assuming that doneInAnotherQueue() executes its closure parameter "sometime in the future", then your task B will likely run before that closure runs (it may not; it's really a race at that point, but probably). If the doneInAnotherQueue() blocks on its closure before returning, then closureParameter will definitely run before task B.
There is absolutely no magic here. The system has no idea what doneInAnotherQueue does with its parameter. It may never run it. It may run it immediately. It may run it sometime in the future. The system just calls doneInAnotherQueue() and passes it a closure.
I rewrote async in normal "function with parameters" syntax to make it even more clear that async() is just a function, and it takes a closure parameter. It also isn't magic. It's not part of the language. It's just a normal function in the Dispatch framework. All it does it take its parameter, put it on a dispatch queue, and return. It doesn't execute anything. There's just closures that get put on queues, scheduled, and executed.
Swift is in the process of adding structured concurrency, which will add more language-level concurrency features that will allow you to express much more advanced things than the simple primitives provided by GCD.
Your task A returns straight away. Dispatching work to another queue is synchronous. Think of the block (the trailing closure) after 'doneInAnotherQueue' as just an argument to the doneInAnotherQueue function, no different to passing an Int or a String. You pass that block along and then you return immediately with the closing brace from task A.

Multiple Completion blocks

I was following this tutorial:
https://medium.com/swift2go/building-grpc-client-ios-swift-note-taking-app-6133c7d74644
But I dont understand this piece of code as it got multiple completion handlers and I dont understand how this code works (I know this is part of a singleton class but what this code is doing and what is "notes?.notes" ???:
func listNotes(completion: #escaping([Note]?, CallResult?) -> Void) {
_ = try? client.list(Empty(), completion: { (notes, result) in
DispatchQueue.main.async {
completion(notes?.notes, result)
}
})
}
Im stuck at this point from 8 days straight so please help me :(
listNotes(_:) has its own completion block named completion.
In the body of the listNotes(_:)'s completion, listNotes(_:) calls an asynchronous, throwing function on the client variable named list(_:, _:).
The function list(_:, _:) has its own completion block and passes two variables into it; notes and result.
When list(_:, _:)'s completion block executes, the first thing that happens is it creates a GCD block to execute listNotes(_:)'s completion on the main thread.
Then it passes two block variables from list(_:, _:)'s closure forward into listNotes(_:)'s closure; a property on the optional variable notes also named notes (should be refactored imo) and result.
One very important thing to note here is that because client.list(_:, _:) is a throwing function and the error is never caught, if that function does throw, listNotes(_:) will never execute its completion block. I generally consider this to be very bad practice since someone could depend on that function to execute its closure regardless of success. You're essentially setting yourself up to break a promise you made in the function signature.

Does return need to be called after an escaping completion block?

If I have the following code:
func getData(completion: #escaping () -> ()) {
// Do stuff
completion()
print(hello)
}
When the completion block is called, will the function automatically return, or will it continue running and "hello" be printed?
Completion handlers are just like any other ordinary closure. In some cases people might want to run some more code after completion, so that's why they designed them like so. When you call them, they run. And after they return, the code below runs. They cannot replace a return statement. If you want to stop running the rest of the method after the completion() bit, put a return there.
Unless, your completion handler is declared to return Never, in which case it will either terminate your whole app, or run indefinitely. In that case any code after it won't be run. :)

Swift 3.0: Does calling `sync` on a queue after calling `async` block the queue?

I was going through the revisions in the Swift document and found the following
If you need to capture and mutate an in-out parameter, use an explicit local copy, such as in multithreaded code that ensures all mutation has finished before the function returns.
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
// Make a local copy and manually copy it back.
var localX = x
defer { x = localX }
// Operate on localX asynchronously, then wait before returning.
queue.async { someMutatingOperation(&localX) }
queue.sync {}
}
I had two questions concerning this:
Does calling async and then calling sync block the queue?
Why would you call async in the first place if you wanted to wait? I always thought asynchronous tasks were to return immediately without waiting until the whole code block was executed. Shouldn't one call sync?
EDIT Added link to the document. BTW I don't think whether queue is serial or concurrent is not too relevant.

Resources