0

I need to return a value from a function that has a closure in it.

I researched about returning value from closures, and found out that I should use 'completion handler' to get the result I want.

I saw posts here and articles explaining it but could not apply because I didn't find anything that matches with my problem.

class ViewController: UIViewController {

    let urls = URLs()

    override func viewDidLoad() {
        super.viewDidLoad()

        var leagueId = getLeagueId(country: "brazil", season: "2019")
        print(leagueId) //PRINTING 0

    }

    func getLeagueId (country: String, season: String) -> Int {

        let headers = Headers().getHeaders()
        var leagueId = 0
        let url = urls.getLeagueUrlByCountryAndSeason(country: country, season: season)


        Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON {
            response in
            if response.result.isSuccess {
                let leagueJSON: JSON = JSON(response.result.value!)
                leagueId = (leagueJSON["api"]["leagues"][0]["league_id"].intValue)

            }
            else {
                print("error")
            }
        }
           return leagueId
    }
}

The value returned is always 0 because the closure value is not passing to the function itself.

Thanks a lot

  • 1
    func getLeagueId is not returning anything. If you wish to transfer the data after network call then you need to make use of closures. – Anuraj Jul 29 '19 at 11:06
  • Sorry I will edit it. It was returning an Int value but I changed it to try to use completion handler – Leonardo D'Amato Jul 29 '19 at 11:08
  • http://www.programmingios.net/returning-a-value-from-asynchronous-code/ – matt Jul 29 '19 at 11:09
  • 2
    Alamofire.request is asynchronous task so leagueId is return 0 first and than Alamofire.request call that's way you get 0 value. – Kishan Suthar Jul 29 '19 at 11:11

4 Answers4

3

So the reason why you're having this issue is because AlamoFire.request is asynchronous. There's a great explanation of asynchronous vs synchronous here But basically when you execute something asynchronously, the compiler does not wait for the task to complete before continuing to the next task, but instead will execute the next task immediately.

So in your case, the AlamoFire.request is executed, and while it's running, the next line after the block is immediately run which is the line to return leagueId which will obviously still be equal to zero since the AlamoFire.request task (function) has not yet finished.

This is why you need to use a closure. A closure will allow you to return the value, after AlamoFire.request (or any other asynchronous task for that matter) has finished running. Manav's answer above shows you the proper way to do this in Swift. I just thought I'd help you understand why this is necessary.

Hope this helps somewhat!

Edit:

Manav's answer above is actually partially correct. Here's how you make it so you can reuse that value the proper way.

var myLeagueId = 0;
getLeagueId(country: "brazil", season: "2019",success: { (leagueId) in

        // leagueId is the value returned from the closure
        myLeagueId = leagueId
        print(myLeagueId)
    })

The code below will not work because it's setting myLeagueId to the return value of getLeagueId and getLeagueId doesn't have a return value so it won't even compile.

myLeagueId = getLeagueId(country: "brazil", season: "2019",success: { (leagueId) in
        print(leagueId)
    })
adeiji
  • 550
  • 1
  • 3
  • 13
  • Thank you very much for you awesome explanation. Manav’s answer worked for me as I could print the value but what I really wanted to do I am still not able that is assign this value to a variable and then use it wherever I want. Thanks again – Leonardo D'Amato Jul 29 '19 at 12:07
2

You need to either return value from the function.

func getLeagueId (country: String, season: String)->Int

Else you need to use completion handlers.

func getLeagueId (country: String, season: String,success:@escaping (leagueId: Int) -> Void) {

    let headers = Headers().getHeaders()
    var leagueId = 0
    let url = urls.getLeagueUrlByCountryAndSeason(country: country, season: season)


    Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON {
        response in
        if response.result.isSuccess {
            let leagueJSON: JSON = JSON(response.result.value!)
            leagueId = (leagueJSON["api"]["leagues"][0]["league_id"].intValue)
            success(leagueId)

        }
        else {
            print("error")
        }
    }
}

And then use it in your code :

  getLeagueId(country: "brazil", season: "2019",success: { (leagueId) in
            print(leagueId)
self.leagueId = leagueId
        })
Manav
  • 2,284
  • 1
  • 14
  • 27
  • I did. I could print the right value to the screen. Ok. But I couldn't assign the value to a variable to use later on – Leonardo D'Amato Jul 29 '19 at 11:38
  • @LeonardoD'Amato Where are you assigning the value? – Anuraj Jul 29 '19 at 11:40
  • @Anuraj I tried this way: override func viewDidLoad() { super.viewDidLoad() var leagueId = 0 var id = getLeagueId(country: "brazil", season: "2019", success: { (id) in leagueId = id print(id) }) } – Leonardo D'Amato Jul 29 '19 at 11:42
  • leagueId is declared in viewDid load it won't be available to the viewController. – Anuraj Jul 29 '19 at 12:08
  • 1
    Sorry I copy pasted your code. So as the function does not return anything , you need to assign the value in completion block. – Manav Jul 29 '19 at 12:23
  • Hi @Manav, still did not manage of how to pass this value to a normal variable in my code. I tried with the completion block as I researched but nothing worked – Leonardo D'Amato Jul 30 '19 at 09:09
1

This is how you should implement completionBLock

func getLeagueId (country: String, season: String, completionBlock:((_ id: String, _ error: Error?) -> Void)?) {

            let headers = Headers().getHeaders()
            var leagueId = 0
            let url = urls.getLeagueUrlByCountryAndSeason(country: country, season: season)


            Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON {
                response in
                if response.result.isSuccess {
                    let leagueJSON: JSON = JSON(response.result.value!)
                    if let leagueId = (leagueJSON["api"]["leagues"][0]["league_id"].intValue){
                      completionBlock?(leagueId,nil) 
                   }else {
                        completionBlock?(nil,nil) // PASS YOUR ERROR
                     }
                }
                else {
                      completionBlock?(nil,nil) // PASS YOUR ERROR
                }
            }
        }
Anuraj
  • 1,242
  • 10
  • 25
  • 1
    @Kamran I didn't check whether it will compile or not. I just said this how completion blocks are implemented. It will be fine if you could point out the error. May it has errors I just copied the text and edited it. – Anuraj Jul 29 '19 at 11:31
0

func getLeagueId isn't return anything so you get 0, if you want to get the result from func getLeagueId you should add completion handler function that will update this value.

user2067656
  • 426
  • 3
  • 7