Returning Data from Async Call in Swift Function

How I can return value from async block in swift

Like @rmaddy said, you have no other way than to use completion handlers.

func getAppConfigFromDB(_ key: String, completion: @escaping ((String) -> Void)) {
let value = String()
backgroundthread.async {
let inst = AppConfigDB.init(_APP_CONFIG_DB_PATH)
value = inst.getConfigurationInfo(key) // I want to return from here.
completion(value)
}
}

You call the method like this.

getAppConfigFromDB("") { (value) in
// Use value to do something
}

swift calling async function without a return value

What you're describing is a Task. For example:

Task { await `do`() }
stuff()

This will run do() concurrently with stuff(). If you need to keep track of when do() completes, you can await the task's value:

let task = Task { await `do`() }
stuff()
await task.value // Doesn't actually return anything, but will block

This kind of Task runs in the context of the current Actor, which is usually what you want. If you want something independent of the current Actor, you can use Task.detached() instead.

If you've previously used DispatchQueues, in many of the places you would have written queue.async { ... }, you can now write Task { ... }. The new system is much more powerful, but it maps fairly nicely to the old system if you want it to.

iOS - Swift - Function that returns asynchronously retrieved value

Yes, it is possible to do this. Its called a closure, or more commonly a callback. A callback is essentially a function that you can use as an argument in another functions. The syntax of the argument is

functionName: (arg0, arg1, arg2, ...) -> ReturnType

ReturnType is usually Void. In your case, you could use

result: (image: UIImage?) -> Void

The syntax of calling a function with one callback in it is

function(arg0, arg1, arg2, ...){(callbackArguments) -> CallbackReturnType in
//code
}

And the syntax of calling a function with several callbacks is (indented to make it easier to read)

function(
arg0,
arg1,
arg2,
{(cb1Args) -> CB1Return in /*code*/},
{(cb2Args) -> CB2Return in /*code*/},
{(cb3Args) -> CB3Return in /*code*/}
)

If your callback function escapes the main function (the callback is called after the main function returns), you must add @escaping in front of the callback's argument type

You're going to want to use a single callback that will be called after the function returns and that contains UIImage? as the result.

So, your code could look something like this

func imageFromFile(file: PFFile, result: @escaping (image: UIImage?) -> Void){
var image: UIImage?

file.getDataInBackgroundWithBlock() { (data: NSData?, error: NSError?) -> Void in
//this should be 'error == nil' instead of 'error != nil'. We want
//to make sure that there is no error (error == nil) before creating
//the image
if error == nil {
image = UIImage(data: data!)
result(image: image)
}
else{
//callback nil so the app does not pause infinitely if
//the error != nil
result(image: nil)
}
}
}

And to call it, you could simply use

imageFromFile(myPFFile){(image: UIImage?) -> Void in
//use the image that was just retrieved
}

Returning data from async call function and save variables after calling webservice in swift

First, there's no point in using resultadoWebService. So I'd simplify your snippet like so:

WebService.llamarWebService { datos in
print([datos[0],datos[1],datos[2],datos[3],datos[4]])
}

Second, you asked about updating variables. Actually, though, there's no point in updating local variables, but perhaps you want to update properties of your calling object. If so, update them inside the closure, and then trigger whatever UI update (or whatever), is appropriate for the results:

var var1: String?
var var2: String?

func performRequestAndUpdateUI() {
WebService.llamarWebService { datos in
guard datos.count == 5 else {
print("didn't get the number of items data I expected")
return
}

guard datos[0] == "OK" else {
print("not everything was OK")
return
}

// note, you probably want to dispatch all model updates to the main queue
// to simplify synchronization. also UI updates must happen on main queue,
// too.

dispatch_async(dispatch_get_main_queue()) {
self.var1 = datos[1]
self.var2 = datos[2]
...
// do whatever UI updates you want here
}
}

// but don't try to use `datos` results here, because the above runs
// asynchronously and we don't have the result by the time we get here
// in our code.
}

Now, whether datos will always have five items, and whether you want to check that datos[0] was OK is all up to you, but hopefully it illustrates the idea, namely that you're dealing with an asynchronous method, so you can't just immediately use the values passed back in the closure, but rather you have to constrain your use of them to the closure itself. And if you're updating the model and/or UI, make sure to dispatch that back to the main thread. But you cannot immediately update variables, because the response from the web service is asynchronous, and won't be called until later.


I might suggest a slightly safer (and simpler) retornarDatos, which excises the ! forced unwrapping operators, perhaps using optional chaining like so:

class func retornarDatos(data: NSData) -> [String]? {
return String(data: data, encoding: NSUTF8StringEncoding)?
.componentsSeparatedByString(",")
}

Or, if you really want to return an empty string array upon error like the current rendition, you could do:

class func retornarDatos(data: NSData) -> [String] {
return String(data: data, encoding: NSUTF8StringEncoding)?
.componentsSeparatedByString(",") ?? [String]()
}

By the way, I'd suggest you get your arms around the above code/concepts, but once you do, you may want to reevaluate your web service design. I would suggest considering using JSON for the response, as the parsing process is more robust and it's easier to differentiate between fundamental web service errors and a web service that successfully returned something (whether it was "OK" or not).

Swift write an async/await method with return value

Thanks, vadian comment. I used a closure as an input parameter of the method.

// MARK: - PassCode Methods
func checkPassCode(completionHandler:@escaping (_ flag:Bool) -> ()) {
let storePin = getStorePin()
let userPin = self.pin.joined(separator: "")
AsymmetricCryptoManager.sharedInstance.decryptMessageWithPrivateKey(storePin as Data) { (success, result, error) -> Void in
if success {
let pin = result!
print("userPin is: \(userPin)")
print("storePin is: \(pin)")
completionHandler(userPin == pin)
} else {
print("Error decoding base64 string: \(String(describing: error))")
completionHandler(false)
}
}
}

func getStorePin() -> NSData {
if let pin = self.keychain.get("pin") {
return NSData(base64Encoded: pin, options: []) ?? NSData()
}
return NSData()
}

and then call this method:

checkPassCode { success in
if success {
print("sucess")
} else {
print("not sucess!")
}
}

Swift Function returning a value from asynchronous firebase call

The problem with

var recipients : [String] = [""]
DispatchQueue.main.async {
GetRecipientsFor(GroupChatID: gchatID) { result in
print(result)
recipients = result
}
}
print(recipients) // Completes before recipients = result

is that the last line is happening before the async call.

To explain futher print(recipients) happens before recipients = result. All logic using recipients needs to happen within that completion block. All you need to do is

func getRecipients(completion: @escaping ([String]) -> ()) {
var recipients : [String] = [""]
DispatchQueue.main.async {
GetRecipientsFor(GroupChatID: gchatID) { result in
print(result)
completion(result)
}
}
}

if you want to have further logic included you can call a function within the completion i.e. handleResults(result). I think it would be very beneficial to read more about closures/completion blocks/and async calls.

How to exit a for loop with data from an async function in swift?

To see what the issue is with what you're apparently trying to do, let's simulate it in a simpler way. I'll use an asynchronous random number generator. Try this in a playground:

func delay(_ delay:Double, closure:@escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
func asyncRand(_ completion: @escaping (Int) -> ()) {
let d = Double.random(in: 1...3)
delay(d) {
let i = Int.random(in: 1...100)
completion(i)
}
}
func go() {
for i in 1...12 {
asyncRand() { result in
print(i, result)
}
}
print("finished")
}
go()

The result in a typical run might be:

finished
8 13
1 15
9 9
10 56
7 57
3 87
2 70
11 88
6 82
12 16
4 81
5 46

So there are two issues here, because of the asynchronous nature of the central asyncRand call: the results come back in no order, over time, and the go method itself (the loop) finishes before any results come back, so there is no opportunity to find out which result is the maximum.

To straighten that out, while we are waiting for async/await to appear in Swift (possibly as soon as next week), you can use a dispatch group. Ignoring any threading issues, we might do this:

func go() {
let group = DispatchGroup()
var pairs = [[Int]]()
for i in 1...12 {
group.enter()
asyncRand() { result in
print(i, result)
pairs.append([i,result])
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print("finished")
if let maximum = pairs.max(by: {$0[1] < $1[1]}) {
print(maximum)
}
}
}

Now the result is (for example):

11 52
8 6
2 1
4 6
12 77
1 88
7 45
9 36
6 25
3 22
10 78
5 33
finished
[1, 88]

So you see we have "waited" until all the results have come back, and we have picked out the pair where the largest value was returned.

So you could do something along those lines. But whether that's a good idea is another question.

Synchronous and Asynchronous code behaviour

First of all, you should not show pictures of code, only the code itself. Otherwise it is hard to check your code.

Next, your code does not execute synchronously by design:

The parameter completion of your function checkApp is a block that is executed asynchronously after your function finished.

So, you first call the function and let it execute independently.

Then return status is executed, which is with highest probability false.

Eventually, your function finishes and sets the value of status to whatever is the result of the function, but this is (again with highest probability) not returned.

EDIT:

If you want to wait for the completion to finish (which may be not a good idea on the main thread, because it would be blocked), you can wrap your code in a block like this:

// Before call to checkApp:
let lock = DispatchGroup.init()

// At the end of the completion block:
lock.leave()

// Before return status
let waitResult = lock.wait(timeout: .now() + 100)
if waitResult != .success { fatalError("Timeout") }


Related Topics



Leave a reply



Submit