2

I'm working to an iOS Swift App and I am trying to get the content of a website. The problem is that, when I run the code, the response object is returned after other lines are executed.

I have this:

public var response: Array<Any>?

public func myRequest(completion: @escaping (_ json: Any?, _ error: Error?)->()) {
    var request = URLRequest(url: self.url)
    request.httpBody = postString.data(using: .utf8)
    let t = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data,
        error == nil else {
            completion(nil, error)
            return
        }
        let json = try? JSONSerialization.jsonObject(with: data, options: [])
        if let dictionary = json as? [String : Any] {
            if (dictionary["ok"] as? String == "true") {
                self.response = dictionary["users"] as? Array<Any>
            }
        }
        completion(json, error)
    }
    t.resume()
}

And then:

func foo() {
    myRequest() { json, error in
        print(json)
    }
    print("I'm here!")
}

And I'm getting this:

I'm here
{...} //JSON

The question is: why I'm retrieving I'm here before JSON? How can I solve it?

JackDevelop
  • 23
  • 1
  • 4
  • 4
    Do **NOT**, ever, use "sleep" in this case. Do not wait. Use a callback. Here's an example of what you should do: https://stackoverflow.com/a/37343547/2227743 Another one: https://stackoverflow.com/a/31264556/2227743 And there's many, many other existing answers about this. Search for "callback", "asynchronous" and "completion handler". – Eric Aya Feb 19 '18 at 16:13
  • @the4kman I know is a bad thing in fact my question is: "how can I don't use sleep()?" – JackDevelop Feb 19 '18 at 16:18

1 Answers1

1

Here's an example (based on your code) of how to have myRequest accept a completion block and to call it once the JSON has been deserialized (or not).

public func myRequest(completion: @escaping (_ json: Any?, _ error: Error?)->())
{
    var request = URLRequest(url: self.url)
    request.httpBody = postString.data(using: .utf8)
    let t = URLSession.shared.dataTask(with: request) 
    { data, response, error in
        guard let data = data,
                  error == nil else
        {
            completion(nil, error)
            return
        }
        let json = try? JSONSerialization.jsonObject(with: data, options: [])
        //Do other things
        completion(json, error)
    }
    t.resume()
}

And here's how you would call it:

func foo()
{
    myRequest()
        { json, error in
            // will be called at either completion or at an error.
        }
}

Now if you're NOT on main thread, and truly want to wait for your myRequest() to complete, here's how (there are many ways to do this, btw):

func foo()
{
    let group = DispatchGroup()
    group.enter()

    myRequest()
        { json, error in
            // will be called at either completion or at an error.
            group.leave()
        }
    group.wait() // blocks current queue so beware!
}
Smartcat
  • 2,834
  • 1
  • 13
  • 25
  • The underscore characters (and actually also the parameter labels) in the completion closure are pointless in Swift 3+ – vadian Feb 20 '18 at 05:30
  • @vadian There is a point to them: they make the method more readable, so you don't need to provide a method comment or expect the caller to dig through your method just to figure out what the completion's parameters are really for. It's good to always code as if someone else is going to use it. Plus, providing the labels make writing a method comment easier, for the comment text can refer to them by names. – Smartcat Feb 20 '18 at 05:42
  • Primarily I mean the underscore characters. They are Swift 2 legacy where parameter labels have been used (or could be omitted) in the closure call. – vadian Feb 20 '18 at 05:47
  • The underscores are required, at least in Swift 4, anytime the labels are provided. Otherwise you'll get a compile error such as: "Function types cannot have argument labels; use '_' before 'json'" – Smartcat Feb 20 '18 at 05:48
  • @Smartcat I tried it and it is not working. When `foo()` is executed the script goes on and executes all the lines after `foo()`. The problem is persisting: the response (`json`) is returned after other code is executed. – JackDevelop Feb 20 '18 at 15:14
  • Oh, didn't know you wanted that. I've edited answer to cover that. Just beware that if you intend to wait on the main thread, you need to re-think your app architecture instead. Provide a progress spinner, for instance, which turns off whenever the completion block completes. – Smartcat Feb 20 '18 at 15:34