-1

Assuming "request" is defined and valid

func1()
// start using "X"

func func1() {
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        // code, that eventually gets some values "a" and "b"
        self.func2(a: a, b: b)
    }.resume()
}

func func2(a: String, b: String) {
    DispatchQueue.main.async {
        // some UI stuff that needs to be done on the main thread
        self.func3(a: a, b: b)
    }
}

func func3(a: String, b: String) {
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        // code, that eventually gets a value "X"
        // HOW TO RETURN "X" ALL THE WAY BACK WHERE func1() IS CALLED?
    }.resume()
}

I just typed this up manually, so there may be typos and other syntax errors (feel free to point them out)

So I have a few functions that I need to run one after another, which I can do, however, the last function generates a value "X", which is the value that I want. How can I return "X" from func3() all the way back to where func1() is called so that I can carry on and use it?

ooh
  • 9
  • 5
  • 1
    Do you understand that you can't `return` the value normally because the code is async, and have to use completion handlers? Are you asking about how to use completion handlers? – Sweeper Jul 06 '20 at 01:56

1 Answers1

0

Give completion handlers to every function in the chain. It's easier to start with func3.

func func3(a: String, b: String, completion: @escaping (TypeOfX) -> Void) {
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        // code, that eventually gets a value "X"
        let x = getX()
        completion(x)
    }.resume()
}

And then func2 and func1 are forced to change...

func func2(a: String, b: String, completion: @escaping  (TypeOfX) -> Void) {
    DispatchQueue.main.async {
        // some UI stuff that needs to be done on the main thread
        self.func3(a: a, b: b, completion: completion)
    }
}

func func1(completion: @escaping (TypeOfX) -> Void) {
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        // code, that eventually gets some values "a" and "b"
        self.func2(a: a, b: b, completion: completion)
    }.resume()
}

And finally the caller can pass a closure and use x there:

func1 {
    x in
    // use x here!
}
// Note that you can't use x here, outside the closure, because this line will be run 
// immediately after func1 returns, and before you get x

EDIT:

If you want to use x elsewhere, you can pass it to a method (and optionally store it in a property). Here are two ways. Which way you choose depends highly on the context - what func1 is responsible for, where is it called, etc.


var x: TypeOfX?

...

func1 {
    x in
    // doesn't have to be "self.", could be on any object, really
    self.x = x
    self.didRemotelyGetX()
}

...

func didRemotelyGetX() {
    // use x here...
}

var x: TypeOfX?

...

func1 {
    x in
    self.didRemotelyGetX(x)
}

...

func didRemotelyGetX(_ x: TypeOfX) {
    // use x here...
    // self.x = x // do this if you want
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • thank you for this! i was able to do this before asking the question, however i left out my completion handlers because i wasn't sure if there was a better way. my question is that how can i set it up so that i CAN use "x" outside the closure? edit: deleted my other comment but i see it was answered already so ill put it back: i pointed out that the completions should be @escaping – ooh Jul 06 '20 at 02:06
  • @ooh In the closure, you can assign `x` to a global variable or something, I guess... But you still need to wait for the code that gets `x` to be executed. How do you know when that is (reliably)? Well, the completion handler... You can also call another method in the completion handler and pass `x` to it. Does that count as "outside the completion handler"? Whatever you do, it will probably trace back to the completion handler. – Sweeper Jul 06 '20 at 02:11
  • So if "x" is a value that is assigned to a class property, and an instance of the class is declared elsewhere (a view controller), how I ensure that X is set before trying to use it in the view controller? Tempted to throw in a `while classInstance.X == nil {}` but surely there is a better way. (You may have addressed this and I may have misunderstood) – ooh Jul 06 '20 at 02:25
  • @ooh Quoting my previous comment: "How do you know when that is (reliably)? Well, the completion handler..." So in the completion handler, you would set the property, and then maybe call a method of the VC to tell the VC that the property is set. – Sweeper Jul 06 '20 at 02:27
  • So what would the code in the VC look like? I have `let a = ClassName()` and want to use `a.X`. Would there be some sort of while loop to check if it is set? I don't understand how to utilize this method telling the VC it is set as far as the code written. Sorry to be annoying, just having a hard time picturing this code-wise. Maybe a quick writeup of sample code would help. – ooh Jul 06 '20 at 02:46
  • @ooh It is very hard to suggest anything without knowing what `func1`, `func2` etc actually do, and where you are calling these things. But see the edit anyway :) – Sweeper Jul 06 '20 at 02:55