8

I am getting very similar problems to this post, but I don't fully understand the answer. I've created a completion handler, but it doesn't seem to be working as expected.

func updateTeam(teamID: Int) {
    startConnection {NSArray, Int in
        //Do things with NSArray
    }
}

func startConnection(completion: (NSArray, Int) -> Void) {
    let url = URL(string: "http://www.example.com/path")
    var request : URLRequest = URLRequest(url: url!)
    request.httpMethod = "POST"
    let postString = "a=\(Int(teamInput.text!)!)"
    request.httpBody = postString.data(using: .utf8)

    let dataTask = URLSession.shared.dataTask(with: request) {
        data,response,error in
        print("anything")
        do {
            if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
                self.teamResult = jsonResult
                print(jsonResult)
            }
        } catch let error as NSError {
            print(error.localizedDescription)
        }

    }
    dataTask.resume()

    completion(NSArray(object: teamResult), Int(teamInput.text!)!)
}

Nothing within the dataTask statement seems to run, or at least it doesn't complete before I try to use the data resulted. What is wrong with this completion handler?

Thank you in advance!

Community
  • 1
  • 1
D. Cohen
  • 403
  • 1
  • 6
  • 17
  • I think that `completion(NSArray(object: teamResult), Int(teamInput.text!)!)` should go where you have `print(jsonResult)`. The way it is now, you start the data task then immediately call the completion handler with (probably) `nil`. If you breakpoint inside your datatask handler (on any of the print methods), are the breakpoints hit? – keithbhunter Mar 09 '17 at 21:01
  • 1
    I highly recommend you look up Alamofire and use it if you can for this project, it makes code like this MUCH easier to write – Daniel Legler Mar 09 '17 at 21:15
  • yes, moving the handler to the data task seemed to work beautifully. Thank you so much! – D. Cohen Mar 09 '17 at 21:18
  • @Daniel Legler, I'll definitely look into it – D. Cohen Mar 09 '17 at 21:18

3 Answers3

11

Your code is structured incorrectly.

URLSession creates tasks that are run asynchronously. You set up a task, and either pass in a completion block, or set up a delegate.

The task.resume() call returns immediately, long before the network download is complete.

Once the task is complete, the system calls your completion handler (or your delegate, if you use the delegate style).

Beware that URLSessions' completion handlers and delegate calls are done on a background thread. If you do any UIKit calls in response to a task completing, you need to do it on the main thread.

As @keithbhunter says in his comment, you need to put the call to your completion handler inside the completion handler for your task. It's probably safest if you wrap that whole completion handler call in a call to the main thread:

func startConnection(completion: (NSArray, Int) -> Void) {
    let url = URL(string: "http://www.example.com/path")
    var request : URLRequest = URLRequest(url: url!)
    request.httpMethod = "POST"
    let postString = "a=\(Int(teamInput.text!)!)"
    request.httpBody = postString.data(using: .utf8)

    let dataTask = URLSession.shared.dataTask(with: request) {
        data,response,error in
        print("anything")
        do {
            if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
                self.teamResult = jsonResult
                print(jsonResult)
                //Use GCD to invoke the completion handler on the main thread
                DispatchQueue.main.async() {
                  completion(NSArray(object: teamResult), Int(teamInput.text!)!)
                }
            }
        } catch let error as NSError {
            print(error.localizedDescription)
        }
    }
    dataTask.resume()
}

Note that your force-unwrapping of teamInput.text is very fragile, and will crash if teamInput.text is nil, or if it can't be converted to an Int. You'd be much better off to write your completion handler to take optionals for both the data and the int value you get back from teamInput.text:

func startConnection(completion: (NSArray?, Int?) -> Void) {

and call it passing in an optional:

let value: Int? = teamInput.text != nil ? Int(teamInput.text!) : nil
completion(NSArray(object: teamResult), value)
Duncan C
  • 115,063
  • 19
  • 151
  • 241
3

I think you should also handle error on closure.

func updateTeam(teamID: Int) {
    startConnection {array, teamId, error in
        // Do things with NSArray or handle error
    }
}

func startConnection(completion: @escaping (NSArray?, Int, Error?) -> Void) {
    let url = URL(string: "http://www.example.com/path")
    var request : URLRequest = URLRequest(url: url!)
    request.httpMethod = "POST"
    let postString = "a=\(Int(teamInput.text!)!)"
    request.httpBody = postString.data(using: .utf8)

    let dataTask = URLSession.shared.dataTask(with: request) {
        data,response,error in
        print("anything")
        do {
            if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
                self.teamResult = jsonResult
                print(jsonResult)
                DispatchQueue.main.async() {
                    completion(NSArray(object: self.teamResult), Int(teamInput.text!)!, nil)
                }
        } catch let error as NSError {
            print(error.localizedDescription)
            DispatchQueue.main.async() {  
                completion(nil, Int(teamInput.text!)!, error)
            }
        }

    }
    dataTask.resume()
}
abdullahselek
  • 6,297
  • 2
  • 39
  • 32
0

Try this:

let urlString = "www.yoururl.com"
let url = URL(string: string.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)

This has helped for me a lot of times

henrik-dmg
  • 1,211
  • 13
  • 20