3

Any help would be appreciated, I have a bunch of classes that used to call the NSURLConnection Synchronous method, but that does not exist anymore. How do I call this block of code in a way that waits for the response before it moves on?

class func login(email:String,password:String)->Int{
    var userId = 0
    let postEndpoint:String = "https://www.testsite.com/user/login.php"
    let url = NSURL(string: postEndpoint)!
    let urlRequest = NSMutableURLRequest(URL: url, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 15.0)
    urlRequest.HTTPMethod = "POST"
    let body = "email=\(email)&password=\(password)".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
    urlRequest.HTTPBody = body
    let task = NSURLSession.sharedSession().dataTaskWithRequest(urlRequest) {(data, response, error) in
        if data != "" && error == nil{
            do {
                let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! [String: AnyObject]
                if let id = json["id"] as? Int {
                    print("found id:\(id)")
                    userId = id
                }                
            } catch let error as NSError {
                print("Failed to load: \(error.localizedDescription)")
                userId = 2
            }

        }
        else{
            userId = 2
        }
    }
    task.resume()
    return userId
}

And the code calling this...

success = User.login(emailTextField.text!, password: passwordTextField.text!)
print("success last: \(success)")

The problem is the "success last:" is called before the user id is returned. Aaarrrrgggghhhhh!

Pang
  • 9,564
  • 146
  • 81
  • 122
  • You cannot do what you're trying to do. `login` will finish before the `dataTaskWithRequest(urlRequest)` completion handler ever runs. That's what it means to be asynchronous! The correct way is to pass in a completion handler of your own, and call it at the end of the other completion handler. See for example my answer here: http://stackoverflow.com/a/31064653/341994 – matt Nov 03 '15 at 02:13
  • You can't, but you can use `NSTimer` or **NSURLSession** delegate method to run the function after certain data transfer. – Brian Nezhad Nov 03 '15 at 03:31

1 Answers1

3

I asked the very same question a few years back. I didn't receive an answer for that one. People made the very same comments then as is now. I do think that completion handler is a much better idea but now that you are using Swift, you can do this with Grand Central Dispatch:

class func login(email:String,password:String)->Int{
    var userId = 0
    let postEndpoint:String = "https://www.testsite.com/user/login.php"
    let url = NSURL(string: postEndpoint)!
    let urlRequest = NSMutableURLRequest(URL: url, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 15.0)
    urlRequest.HTTPMethod = "POST"
    let body = "email=\(email)&password=\(password)".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
    urlRequest.HTTPBody = body

    let group = dispatch_group_create()

    // Signal the beginning of the async task. The group won't
    // finish until you call dispatch_group_leave()
    dispatch_group_enter(group)

    dispatch_group_async(group, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
        let task = NSURLSession.sharedSession().dataTaskWithRequest(urlRequest) {(data, response, error) in
            if data != "" && error == nil{
                do {
                    let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! [String: AnyObject]
                    if let id = json["id"] as? Int {
                        print("found id:\(id)")
                        userId = id
                    }
                } catch let error as NSError {
                    print("Failed to load: \(error.localizedDescription)")
                    userId = 2
                }
            }
            else {
                userId = 2
            }

            // Signal the end of the async task
            dispatch_group_leave(group)
        }
        task.resume()
    }

    // Wait 30 seconds for the group to be done
    let timeout = dispatch_time(DISPATCH_TIME_NOW, 30_000_000_000)
    dispatch_group_wait(group, timeout)

    return userId
}

If you don't want it to ever timeout, replace that second last line with:

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

What this is useful for:

Let's say you have to call 3 web services. Your application cannot proceed until all 3 have completed. You can call the first service, then call the second in its completion block and so on. However, you pay a time penalty for waiting.

With this pattern, you can call all 3 simultaneously:

let group = dispatch_group_create()
dispatch_group_enter(group)
dispatch_group_enter(group)
dispatch_group_enter(group)

dispatch_group_async(group, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
    let task1 = NSURLSession.sharedSession().dataTaskWithRequest(request1) {...
        // handle response & error
        dispatch_group_leave()
    }

    let task2 = NSURLSession.sharedSession().dataTaskWithRequest(request2) {...
        // handle response & error
        dispatch_group_leave()
    }

    let task3 = NSURLSession.sharedSession().dataTaskWithRequest(request3) {...
        // handle response & error
        dispatch_group_leave()
    }
}

let timeout = dispatch_time(DISPATCH_TIME_NOW, 30_000_000_000)
dispatch_group_wait(group, timeout)

// proceed with your code...
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • I will have time to give this a shot tomorrow and let you know how it goes. I am self taught, don't do this full time, and I really appreciate the help. I spent time trying to get a completion handler working but still had problems with the timing. I'm going to have to learn those better. Anyway, thanks so much! A little kindness goes a long way. – user1495144 Nov 04 '15 at 05:15
  • The dispatch group worked great. I will continue to wrap my head around completion handlers and use those once I get them dialed in, Thanks! – user1495144 Nov 04 '15 at 17:11