Variable becomes nil outside completion handler [duplicate] - ios

This question already has an answer here:
Swift || Returning a class that can be used by other methods from an API call
(1 answer)
Closed 3 years ago.
I am not sure if this is a duplicate or not, I searched and couldn't find an answer. If it is please point me to the similar question.
I am setting the value of the imageURL variable inside a completion handler:
var imageURL: URL?
let options = PHContentEditingInputRequestOptions()
imageAsset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput: PHContentEditingInput?, _) in
imageURL = contentEditingInput?.fullSizeImageURL
})
//prints nil
print(imageURL)
Now, the value of imageURL inside the handler is not nil, but it becomes nil right after (in the print statement). Why is this happening and how can I fix it without having to write all of my subsequent code inside the completion handler block?

You can't "fix" it in the way you'd like, unfortunately. The completion handler here might in theory be called synchronously (ie, at the time of the call to requestContentEditingInput), but can (and most likely will) be called at some time later, when the asset is ready. That could include actual downloading or any other unpredictable time-consuming preparation of the asset that happens on some other thread.
In other words, the function requestContentEditingInput returns to you right away (and your code continues executing), but the function also commences doing some work in the background. That background work, when it finishes, calls your handler block.
The nature of execution flow means that you simply cannot guarantee (and certainly cannot assume) that the handler will be called before execution moves on to your print(imageURL) line.
This kind of asynchronicity is a very common pattern, though! Nothing to be afraid of. You need to put any code that must run subsequently within that handler block (or call out from the handler block to another function if that is cleaner within your file).

It is likely that the completion is not called until later because the code is running on another thread. Try putting a print statement inside of the completion block to see what order the code is executed.

When you work with handlers, the time with the threads can be different every build.
I recommend you create a method that will be called inside the handler.
Like this:
func edit(){
var imageURL: URL?
let options = PHContentEditingInputRequestOptions()
imageAsset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput: PHContentEditingInput?, _) in
imageURL = contentEditingInput?.fullSizeImageURL
display(url:imageURL)
})
}
func display(url: String) {
print(imageURL)
}

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.

How do I wait for an asynchronous call in Swift?

So I've recently come back to Swift & iOS after a hiatus and I've run into an issue with asynchronous execution. I'm using Giphy's iOS SDK to save myself a lot of work, but their documentation is pretty much nonexistent so I'm not sure what might be happening under the hood in their function that calls their API.
I'm calling my function containing the below code from the constructor of a static object (I don't think that's the problem as I've also tried calling it from a cellForItemAt method for a Collection View).
My issue is that my function is returning and execution continues before the API call is finished. I've tried utilizing DispatchQueue.main.async and removing Dispatch entirely, and DispatchGroups, to no avail. The one thing that worked was a semaphore, but I think I remember reading that it wasn't best practice?
Any tips would be great, I've been stuck on this for waaaaaay too long. Thanks so much in advance
GiphyCore.shared.gifByID(id) { (response, error) in
if let media = response?.data {
DispatchQueue.main.sync {
print(media)
ret = media
}
}
}
return ret
My issue is that my function is returning and execution continues before the API call is finished.
That's the whole point of asynchronous calls. A network call can take an arbitrary amount of time, so it kicks off the request in the background and tells you when it's finished.
Instead of returning a value from your code, take a callback parameter and call it when you know the Giphy call has finished. Or use a promise library. Or the delegate pattern.
The one thing that worked was a semaphore, but I think I remember reading that it wasn't best practice?
Don't do this. It will block your UI until the network call completes. Since you don't know how long that will take, your UI will be unresponsive for an unknown amount of time. Users will think your app has crashed on slow connections.
You could just add this inside a method and use a completion handler and therefore do you not need to wait for the response. You could do it like this:
func functionName(completion: #escaping (YOURDATATYPE) -> Void) {
GiphyCore.shared.gifByID(id) { (response, error) in
if let media = response?.data {
completion(media)
return
}
}
}
Call your method like this
functionName() { response in
DispatchQueue.main.async {
// UPDATE the UI here
}
}

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.

How do I wait for another class's functions to complete calling next function

I'm writing an app in Swift and I've run into a problem I can't find much help for.
So I've got a ViewController and a class named CameraHandler that uses calls the an ActionSheet which then presents camera or gallery picker and saves resulting images.
What I want to do is: call camera, save the image, and then store relevant information to an object.
CameraHandler.shared.showActionSheet(vc: self, reg: car.registration)
self.car.imagePath = CameraHandler.shared.returnFilePath()
self.storeCar() // completes before CameraHandler can get imagepath
My workaround is using a simple DispatchQueue to wait 10 secs hoping it completes in time.
As matt says in his comment, don't wait. Don't expect the answer to be returned from your function. You need to rewrite your showActionSheet() function to take a completion handler. (That is a block of code {more specifically, a closure} that you pass as a parameter.) When your function gets an answer from the async process it needs to complete (fetching a value from a remote server, prompting the user for something, etc.) then it calls the completion handler.
See my answer to the thread below. I include a sample project that uses a function with a completion handler to fetch data from a remote server.
Swift: Wait for Firebase to load before return a function
Using a closure or block in other programing languages as a completion handler, let take an example.
func returnFilePath(completionHandler: ((String) -> ()) {
// after get file path
completionHandler(path)
}
then you can use it like:
returnFilePath { path in
// do some thing with the path
}

What does it mean to call an escaping closure after the function returns? [duplicate]

This question already has answers here:
Escaping Closures in Swift
(8 answers)
Closed 5 years ago.
I was reading the apple developer documentation's definition of escaping closures. It says "a closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns"
I am not sure what the last part is supposed to mean, what does it mean by "after the function returns"? Does it mean "after the function returns a value"?
Take for example a call to an API. This call is going to take time, but we are going to want to do something after the call is completed. For example, say we want to refresh a UITableView with the new data that we pull. If we were to do it immediately, the data wouldn't have been received yet:
ApiObject.getObjects(completion: { (error, objects) in })
tableView.reloadData()
If we were to reload the data here, the table view will refresh immediately (before we actually have received the data). By doing it in the completion block, we are saying, run the code when we have completed the function, not when the function actually returns:
ApiObject.getObjects(completion: {(error, objects) in
self.tableView.reloadData()
})
Here we are running it once the objects have been fetched, not once the function itself has reached the end.
Edit
Maybe this will make it easier; I have the following code:
let comeInAnimation = POPBasicAnimation(propertyNamed: kPOPLayoutConstraintConstant)!
comeInAnimation.toValue = 0
comeInAnimation.completionBlock = { (anim, success) -> Void in
self.loginButton.enabled = true
self.signupButton.enabled = true
}
signUpContainingViewLeftConstraint.pop_add(comeInAnimation, forKey: AnimationString.EnterExit.identifier)
This is using the POP animation framework. In this case, I have a login and signup button, but I also have an animation for them to appear. I dont want the buttons to be clicked while they are appearing so i have their enabled set to false originally. Now you can see they get set to enabled in the completionBlock. This means, when the animation is completed, the completion block gets called and I know now is the time to set them to be enabled. Had I done it like so:
let comeInAnimation = POPBasicAnimation(propertyNamed: kPOPLayoutConstraintConstant)!
comeInAnimation.toValue = 0
signUpContainingViewLeftConstraint.pop_add(comeInAnimation, forKey: AnimationString.EnterExit.identifier)
self.loginButton.enabled = true
self.signupButton.enabled = true
Even though the enabled properties are set after the animation is called, the properties are actually being set before the animation is complete. Because the program runs line by line, the animation gets added and then immediately the properties are set (this is too early).
Note: This is an issue because these functions run asynchronously. That is, it allows the program to keep running while it is doing its thing. If this line of code blocked (stopped the program until it was compete) then putting the code in a completion block and putting it immediately after would be the same thing. In real life though, we dont want to block because it gives the appearance that the program has frozen.

Resources