Why an error:"Unexpected non-void return value in void function" happened?

The error is:
Unexpected non-void return value in void function

I don't know how to solve.

Any help is appreciated!!
:arrow_down:This is the code

func NameGet(uid:Double) -> String
{
    let url = URL(string: "http://1.15.82.181:8080/api/userinfo?uid="+String(uid))
    URLSession.shared.dataTask(with: url!) {(data,response,error) in
        do {
            if let d = data {
                let res = try JSON(data:d)
                if let usrname = res[0]["name"].string {
                    return usrname
                }
            }else {
                print("No Data")
            }
        } catch {
            print ("Error")
        }

    }.resume()
}

What are you returning usrname to?

1 Like

NameGet is a function that would return immediately however the dataTask would take longer time to complete.

So it might make sense for you to accept an escaping closure and call the closure after you have received a response from the data task.

Note:

  • It is preferable to have functions start with lowercase (refer: Swift.org - API Design Guidelines)
  • The same task would be simpler if you decided to use swift concurrency

I have made an attempt to fix the issue, please review before using it

func nameGet(uid:Double, completion: @escaping (String?, Error?) -> ()) {
    let url = URL(string: "http://1.15.82.181:8080/api/userinfo?uid="+String(uid))
    URLSession.shared.dataTask(with: url!) { data, response, error in
        
        if let error = error {
            completion(nil, error)
            return
        }
        
        //check response status code etc if needed
        
        //parse data
        guard let data = data else {
            completion(nil, NameError.invalidData)
            return
        }
        
        do {
            let res = try JSON(data:data)
            let usrname = res[0]["name"].string
            completion(usrname, nil)
        } catch {
            completion(nil, error)
        }
        
    }.resume()
}

Error:

enum NameError: Error {
    case invalidData
    case invalidURL
}

Using Swift Concurrency

func nameGet2(uid:Double) async throws -> String {
    guard let url = URL(string: "http://1.15.82.181:8080/api/userinfo?uid="+String(uid)) else {
        throw NameError.invalidURL
    }
    
    let (data, response) = try await URLSession.shared.data(from: url)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw NameError.invalidData
    }
    
    let res = try JSON(data:data)
    let usrname = res[0]["name"].string
    
    return usrname
}
2 Likes

The return usrname is the closure

is only returning the in scope of the closure, it's not returning from the NameGet function. The closure that you're passing to URLSession.shared.dataTask (in a trailing position) has (data,response,error) input, but has Void output, so the error is telling you that in a void function (the closure) you're returning a non-void thing (usrname).

You can't return just a String from your NameGet, because you won't get a String by calling URLSession.shared.dataTask...resume(): your NameGet function must be asynchronous, that is, it must return before actually obtaining the relevant data, that could be produced at any point in the future.

To solve this, you could do a couple of things:

  • if you have access to Combine, you could use URLSession.shared.dataTaskPublisher, that returns Publisher that you then return from the NameGet function;
  • you could return ((String?) -> Void) -> Void from the function, in order to make the clients handle the asynchronism.

In the second case, your code would change like this:

func NameGet(uid:Double) -> (@escaping (String?) -> Void) -> Void
{
  return { gotValue in
    let url = URL(string: "http://1.15.82.181:8080/api/userinfo?uid="+String(uid))
    URLSession.shared.dataTask(with: url!) {(data,response,error) in
        do {
            if let d = data {
                let res = try JSON(data:d)
                if let usrname = res[0]["name"].string {
                    gotValue(usrname)
                }
            }else {
                print("No Data")
                gotValue(nil)
            }
        } catch {
            print ("Error")
            gotValue(nil)
        }
    }.resume()
  }
}

Notice that I used String? instead of String: this way you will inform clients that this is done even if no usrname is produced.

If you can, switching to use Swift's native concurrency with async / await will make this much simpler.

let (response, data) = try await URLSession.shared.data(from: url)
// Check response.
let value = try JSONDecoder().decode(MyType.self, from: data)

You should also switch to using Decodable rather than JSON (which I'm assuming is from SwiftyJSON), as it will give you a much better experience overall.

1 Like

I‘m very thankful for your help!! But I wonder how I can use this function to get the name?

The NameGet function that I wrote is not returning a String anymore, so you can't use it in that context: the Text initializer requires something conforming to StringProtocol, and that closure can't do that.

If used in the context of SwiftUI, @Jon_Shier suggestion is better, and you could use a @StateObject to hold onto a service that fetches your text: take a look at this tutorial.

1 Like