Can Swift Return Value from an Async Void-Returning Block

Can Swift return value from an async Void-returning block?

You should employ asynchronous (ie, escaping) completion handler yourself:

class func checkIfUserExists(uid: String, completion: @escaping (Bool) -> Void) {
userRef.childByAppendingPath(uid).observeSingleEventOfType(.Value) { snapShot in
if snapShot.value is NSNull {
completion(false)
} else {
completion(true)
}
}
}

You can then call this like so:

MyClass.checkIfUserExists(uid) { success in
// use success here
}

// but not here

In your revised question, you demonstrate the use of dispatch groups to make this asynchronous method behave synchronously. (Semaphores are also often used to the same ends.)

Two issues:

  1. This will deadlock if they dispatch their completion handler back to the main queue (and in many cases, libraries will do this to simplify life for us), because you're coincidentally blocking the very same thread they're trying to use. I don't know if that's what they've done here, but is likely.

    If you want to confirm this, temporarily remove dispatch group and then examine NSThread.isMainThread and see if it's running in main thread or not.

  2. You never should block the main thread, anyway. They provided an asynchronous interface for good reason, so you should use asynchronous patterns when calling it. Don't fight the asynchronous patterns, but rather embrace them.

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
}

How can i return value from async block?

You can pass completion closure and after success or failure request execute it.

func xmlRequest(_ id:Int, completion: @escaping ((String) -> Void)){
var array1:Array<Any> = Array<Any>();
array1.append("body" as Any);
array1.append("date" as Any);
array1.append("author_id" as Any);

let con:NSDictionary = NSDictionary();
var field2:Array<Any> = Array<Any>();
field2.append([["id", "=", [id]]]);
let url = Login.BASEURL+XMLRPC_SERVICE_OBJECT;
let param3 = [Login.DATABASE, Login.LOGIN_ID, Login.PASSWORD, "mail.tracking.value","search_read",field2,con] as [Any];

AlamofireXMLRPC.request(url, methodName: "execute_kw", parameters: param3).responseXMLRPC { (response: DataResponse<XMLRPCNode>) in
var valueToReturn:String = "default"
switch response.result {
case .success(let value):
valueToReturn = "success"
case .failure(let error):
valueToReturn = "error"
}

completion(valueToReturn)
}
}

More about closures you can read at Apple documentation =)

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html

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.

Difference between return of Any vs Void in Async Block

Specifying a closure of ([String]) -> Any means that the closure is going to return something, and it is of type Any. But in your examples, (a) your closures are not returning anything at all; and (b) the dropboxWorkoutList does not appear to need/use an object returned by the closure supplied by the caller, anyway. This is a “completion handler closure” pattern, and completion handler closures almost always have a Void return type.

I actually want to use the return values from dropboxWorkoutList to populate a tableview which I've not coded yet

OK, then I think you may be conflating the closure parameter (what dropboxWorkoutList will be supplying back to the caller) and the closure’s return value (the far less common scenario, where the caller needs to supply dropboxWorkoutList some value based upon the closure’s parameter). In this case, you want the former (the closure parameter), not the latter (the closure’s return value).

You likely do not want to change the closure to be ([String]) -> Any, at all, but rather leave it as ([String]) -> Void. The caller should just take the parameter of the closure and use that. For example:

dropboxFunc.dropboxWorkoutList { values in
self.strings = values // update whatever model object your table view data source is using
self.tableview.reloadData() // and tell the table view to reload the data
}

In short, your question here (and in that other post) was, effectively, “how do I return a value using a closure?”, to which the answer is that you should not think of it as “returning” a value. Rather, in asynchronous routines with completion handler closures, results are supplied as parameter(s) to the closure. Essentially, dropboxWorkoutList is calling a function (in this case, a closure) to say “hey, here is your data”. It is providing data to that closure, not returning data from that closure.

Unexpected non-void return value in void function Swift3

The problem is that you are trying to return a non-void value from inside a closure, which only returns from the closure, but since that closure expects a void return value, you receive the error.

You cannot return from an asynchronous function using the standard return ... syntax, you have to declare your function to accept a completion handler and return the value from the async network call inside the completion handler.

func findChat(string: String, completion: @escaping (Chat?)->()) {
var returnValue: (Chat?)
let url = getChatsURL
let Parameters = [ "title" : string ]

Alamofire.request("\(url)", method: .post, parameters: Parameters).validate().responseString { response in
if let anyResponse = response.result.value {
self.responseFromServer = anyResponse
}
if self.responseFromServer == "" {
completion(nil)
} else {
let ref = DatabaseReference.chats.reference()
let query = ref.queryOrdered(byChild: "uid").queryEqual(toValue: (self.responseFromServer))
query.observe(.childAdded, with: { snapshot in
completion(Chat(dictionary: snapshot.value as! [String : Any]))
})
}
}
}

Then you can call this function and use the return value like this:

findChat(string: "inputString", completion: { chat in
if let chat = chat {
//use the return value
} else {
//handle nil response
}
})

'Unexpected non-void return value in void function' in swiftui

You are using an asynchronous method (dataTask). You don't know when it will be finished running (network request). It therefore cannot have a return value. When it finishes it executes the closure (URLSession.shared.dataTask (with: request) {// this block}).

You would certainly like to do it this way:

class DogManager {
var imageInfos: String?

func getFavoriteDoggo(breed: String) {
guard let url = URL(string: "https://dog.ceo/api/breed/\(breed)/images/random") else {
print("Trouble parsing url")
return
}

URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil, (response as? HTTPURLResponse)?.statusCode == 200 else {
return
}
if let data = data {
self.imageInfos = String(data: data, encoding: .utf8)
print(self.imageInfos ?? "no infos")
}
}.resume()
}
}

let manager = DogManager()
manager.getFavoriteDoggo(breed: "retriever/golden")

You can test in a Playground.

Now if you want to use SwiftUI and your View is redrawn when imageInfos changes you have to change your class to ObservableObject:

class DogManager: ObservableObject {
@Published var imageInfos: String?
//....//
}

And use it like this:

struct MainView: View {
@StateObject private var dm = DogManager()

var body: some View {
Text(dm.imageInfos ?? "nothing")
.onAppear {
dm.getFavoriteDoggo(breed: "retriever/golden")
}
}
}

iOS15 :

Note that with the introduction of async / await (iOS15) you can write asynchronous methods that have return values ​​(like you did) :

    @available(iOS 15.0, *)
func getFavoriteDoggo(breed: String) async -> String? {
guard let url = URL(string: "https://dog.ceo/api/breed/\(breed)/images/random"),
let (data, response) = try? await URLSession.shared.data(from: url),
(response as? HTTPURLResponse)?.statusCode == 200 else { return nil }
return String(data: data, encoding: .utf8)
}

You can use it with the new .task modifier :

struct MainView: View {
var dm = DogManager()
@State private var imageInfos: String?
var body: some View {
Text(imageInfos ?? "nothing")
.task {
await imageInfos = dm.getFavoriteDoggo(breed: "retriever/golden")
}
}
}

EDIT :

"Hey thank you for helping me out, but this would only work for only 1
dog breed."

First, let's create a new Dog structure. A Dog has a breed and the information on its image, which initially does not exist (nil).

struct Dog: Identifiable {
let id = UUID()
let breed: String
var imageInfos: String?

init(_ breed: String) {
self.breed = breed
}
}

Our view will show an array of dogs:

@State private var dogs: [Dog] = ["malamute", "chow", "husky", "samoyed"].map(Dog.init)

Now we change our function that fetches the image of a dog: it takes a Dog as a parameter, and returns (when it has finished) a Dog (with imageInfos filled) :

func updateImageOf(dog: Dog) async -> Dog {
var newDog = dog
guard let url = URL(string: "https://dog.ceo/api/breed/\(dog.breed)/images/random"),
let (data, response) = try? await URLSession.shared.data(from: url),
(response as? HTTPURLResponse)?.statusCode == 200 else { return dog }
newDog.imageInfos = String(data: data, encoding: .utf8)
return newDog
}

We create a second function that does the same for several dogs.

func updateImagesOf(favoriteDogs: [Dog]) async -> [Dog] {
var results: [Dog] = []
await withTaskGroup(of: Dog.self) { group in
for dog in favoriteDogs {
group.async {
await self.updateImageOf(dog: dog)
}
}
for await result in group {
results.append(result)
}
}
return results
}

We use this function in our View:

struct MainView: View {
var dm = DogManager()

@State private var dogs: [Dog] = ["malamute", "chow", "husky", "samoyed"].map(Dog.init)

var body: some View {
List(dogs) { dog in
HStack {
Text(dog.breed)
.padding(.trailing, 40)
Text(dog.imageInfos ?? "rien")
}
}
.task {
await dogs = dm.updateImagesOf(favoriteDogs: dogs)
}
}
}

It works (Simulator, Xcode 13 beta2)

how to return value after the execution of the block? Swift

You cannot simply return here as the reverseGeocodeLocation function is running asynchronously so you will need to use your own completion block:

var superPlace: MKPlacemark!

func getPlaceMarkInfo(coordinate: CLLocationCoordinate2D, completion: (superPlace: MKPlacemark?) -> ()) {
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
geocoder.reverseGeocodeLocation(location) { (arrayPlaceMark, error) -> Void in
if error == nil {
let firstPlace = arrayPlaceMark!.first!
let addressDictionaryPass = firstPlace.addressDictionary as! [String : AnyObject]

self.superPlace = MKPlacemark(coordinate: location.coordinate, addressDictionary: addressDictionaryPass)
completion(superPlace: superPlace)
} else {
completion(superPlace: nil)
}
}
}

How to return a value from a void closure in Swift?

Since it's an asynchronous method, you cannot return the value, but instead follow the completion handler pattern, including the data returned as a parameter:

func performAllMatchesQueryWithCompletionHandler(completionHandler: (UIBackgroundFetchResult, [GTLUserUser]?, ErrorType?) -> ()) {
let query = GTLQueryUser.queryForUserList()
query.userBucket = "messages-20-messagestabletes-1465782960"
service.executeQuery(query) { ticket, response, error in
guard error == nil else {
completionHandler(.failed, nil, error)
return
}

if let userCollection = response as? GTLUserCollection, let newUsers = userCollection.items() as? [GTLUserUser] {
completionHandler(.newData, newUsers, nil)
} else {
completionHandler(.noData, nil, nil)
}
}
}

I infer from your use of UIBackgroundFetchResult, that you're doing a background fetch. If so, your performFetchWithCompletionHandler might look like so:

func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
performAllMatchesQueryWithCompletionHandler { fetchResult, users, error in
switch fetchResult {
case .failed:
// do whatever you want if there was an error

case .noData:
// do whatever you want when there is no data

case .newData:
// do whatever you want with `users`, presumably updating your model or what have you
}

completionHandler(fetchResult)
}
}

You could then call this method from viewDidLoad or wherever appropriate for your app.

Note, I removed the leading underscore on the method names, as that's not a common convention in Swift, but call your methods whatever you want. And I renamed _getAllMatches to performAllMatchesQueryWithCompletionHandler, as it makes it more clear that you're performing an asynchronous query.


In comments, you say that you're not doing background fetch, but rather are populating a table. So you might do something like:

func retrieveDataForTableView(tableView: UITableView) {
performAllMatchesQueryWithCompletionHandler { fetchResult, users, error in
switch fetchResult {
case .failed:
// do whatever you want if there was an error

case .noData:
// do whatever you want when there is no data

case .newData:
// do whatever you want with `users`, presumably updating your model or what have you

// once you've updated your model, you can reload the table:

tableView.reloadData()
}
}
}

Note, I've assumed that this completion handler is running on the main thread. If not, you'd want to dispatch_async(dispatch_get_main_queue()) { ... } the code that updates the model and calls reloadData.

Personally, I wouldn't be inclined to use UIBackgroundFetchResult for my performAllMatchesQueryWithCompletionHandler if I wasn't really doing background fetch. I'd probably use my own enumeration for that, to avoid any confusion regarding the intent of this code.

Why i am getting Unexpected non-void return value in void function error while returning value in swift?

I think AuthManager.shared.saveAddressAsWorkHome(params) { (response) in this is asynchronous closure and you are try to return a value in it so you getting this error.

You can not return from asynchronous function directly. You have to add a completion handler on your method and return the value from async in completion handler

So you have to change your function

func getLatDestination(completion : @escaping (Double) -> ()){
var params = [String: Any]()
params[ParametersKeys.access_token] = KeyChain.getAccessToken()!
params[ParametersKeys.address] = googlePlaceObject?.results.first?.formattedAddress
params[ParametersKeys.latitude] = googlePlaceObject?.results.first?.geometry.location.lat
params[ParametersKeys.longitude] = googlePlaceObject?.results.first?.geometry.location.lng
params[ParametersKeys.googlePlaceId] = googlePlaceObject?.results.last?.placeId
params[ParametersKeys.login_type] = 1

AuthManager.shared.saveAddressAsWorkHome(params) { (response) in
if response.flag == RESPONSE_FLAGS.flag_143 {
if let addressData = response.response["addresses"] as? [[String: Any]] {
completion(addressData[0]["lat"])

}
}
}

And when you call your function

getLatDestination(completion: {ret in
print(ret)
})


Related Topics



Leave a reply



Submit