bitops
bitops

Reputation: 4292

iOS takes long time to update view after HTTP request?

I am very new to learning iOS and Swift, so there may be something very basic here that I don't understand. I am using the agent library to send HTTP requests in Swift. Right now I'm just getting the hang of creating the request and parsing the JSON response.

I have a very simple UI with a button and a UILabel that I want to update with the results of some JSON.

Here is my code:

func updateWithJson() -> Void {
    let req = Agent.get("http://xkcd.com/info.0.json")
    req.end({ (response: NSHTTPURLResponse!, data: Agent.Data!, error: NSError!) -> Void in
        let json = data! as Dictionary<NSString, NSString>
        // this takes almost 30 seconds to propagate to the UI...
        self.updateMe.text = json["safe_title"]!
    })
}

The part I don't understand is the self.updateMe.text = json["safe_title"]! statement which is just updating some text on a UILabel called updateMe. It takes almost a full 30 seconds for this to reflect in the UI.

I know that the HTTP request itself is very fast - if I curl that URL it comes back instantaneously. It also seems like the Agent object is making the connection and returning the response pretty fast.

Am I missing something very basic? Should I be updating the UI in a different way? All pointers are appreciated!

UPDATE Through another SO post it's my understanding that the block inside of req.end is a background thread and I need to do UI updates on the main thread. I updated my function to look like this and I get the desired result. However, now I'm wondering if there's something incorrect about doing it this way?

    func updateWithJson() -> Void {
        let req = Agent.get("http://xkcd.com/info.0.json")
        req.end({ (response: NSHTTPURLResponse!, data: Agent.Data!, error: NSError!) -> Void in
            let json = data! as Dictionary<NSString, NSString>
            var text = json["safe_title"]!
            dispatch_sync(dispatch_get_main_queue()) {
                self.updateMe.text = text
            }
        })
    }

Upvotes: 1

Views: 1265

Answers (1)

Mike S
Mike S

Reputation: 42325

What's happening is that your HTTP request is running on a background thread. When it calls the callback you provide, you're still on that background thread. In iOS all UI work must be done on the main thread. The easiest way to do this is by using GCD's dispatch_async like so:

dispatch_async(dispatch_get_main_queue()) {
    [weak self] in
    self?.updateMe.text = json["safe_title"]!
    return
}

So your entire function would look like:

func updateWithJson() -> Void {
    let req = Agent.get("http://xkcd.com/info.0.json")
    req.end({ (response: NSHTTPURLResponse!, data: Agent.Data!, error: NSError!) -> Void in
        let json = data! as Dictionary<NSString, NSString>
        dispatch_async(dispatch_get_main_queue()) {
            [weak self] in
            self?.updateMe.text = json["safe_title"]!
            return
        }
    })
}

The bit about [weak self] in is so you don't create a strong reference cycle. The return at the end is there because you have a closure with only one expression in it. In that case, Swift will try to implicitly return a value based on that statement and in this case that's not what you want.

Upvotes: 6

Related Questions