4

I'm just learning Ios programming for the first time, with Swift and Xcode 6 beta.

I am making a simple test app that should call an API, and then segue programmatically to a different view to present the information that was retrieved.

The problem is the segue. In my delegate method didReceiveAPIResults, after everything has been successfully retrieved, I have:

println("--> Perform segue")
performSegueWithIdentifier("segueWhenApiDidFinish", sender: nil)

When the app runs, the console outputs --> Perform segue, but then there is about a 5-10 second delay before the app actually segues to the next view. During this time all the UI components are frozen.

I'm a little stuck trying to figure out why the segue doesn't happen immediately, or how to debug this!

Heres The Full View controller:

import UIKit

class ViewController: UIViewController, APIControllerProtocol {

    @lazy var api: APIController = APIController(delegate: self)

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func didReceiveAPIResults(results: NSDictionary) {

        println(results)

        println("--> Perform segue")
        performSegueWithIdentifier("segueWhenApiDidFinish", sender: nil)
    }

    @IBAction func getData(sender : AnyObject){

        println("--> Get Data from API")
        api.getInfoFromAPI()

    }
}

And my API controller:

import UIKit
import Foundation

protocol APIControllerProtocol {
    func didReceiveAPIResults(results: NSDictionary)
}

class APIController: NSObject {

    var delegate: APIControllerProtocol?

    init(delegate: APIControllerProtocol?) {
        self.delegate = delegate
    }


    func getInfoFromAPI(){

        let session = NSURLSession.sharedSession()
        let url = NSURL(string: "https://itunes.apple.com/search?term=Bob+Dylan&media=music&entity=album")

        let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
            if(error) {
                println("There was a web request error.")
                return
            }

            var err: NSError?

            var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.    MutableContainers, error: &err) as NSDictionary

            if(err?) {
                println("There was a JSON error.")
                return
            }

            self.delegate?.didReceiveAPIResults(jsonResult)
        })
        task.resume()


    }    
}

UPDATE: Got this working based on Ethan's answer. Below is the exact code that ended up getting the desired behavior. I needed assign that to self to have access to self inside the dispatch_async block.

let that = self

if(NSThread.isMainThread()){
    self.delegate?.didReceiveAPIResults(jsonResult)

}else
{
    dispatch_async(dispatch_get_main_queue()) {
        println(that)
        that.delegate?.didReceiveAPIResults(jsonResult)
    }
}

Interestingly, this code does not work if I remove the println(that) line! (The build fails with could not find member 'didReceiveAPIResults'). This is very curious, if anyone could comment on this...

Shruti Thombre
  • 989
  • 4
  • 11
  • 27
CraexIt
  • 179
  • 3
  • 11
  • I think the 'println()' compilation issue is just a Beta side-effect. I got tons of those weird-ish side-effects. Still a Beta. So... Thanks for the question. Quite interesting. – nembleton Aug 04 '14 at 23:16

2 Answers2

4

I believe you are not on the main thread when calling

self.delegate?.didReceiveAPIResults(jsonResult)

If you ever are curious whether you are on the main thread or not, as an exercise, you can do NSThread.isMainThread() returns a bool.

Anyway, if it turns out that you are not on the main thread, you must be! Why? Because background threads are not prioritized and will wait a very long time before you see results, unlike the mainthread, which is high priority for the system. Here is what to do... in getInfoFromAPI replace

self.delegate?.didReceiveAPIResults(jsonResult)

with

dispatch_sync(dispatch_get_main_queue())
{
    self.delegate?.didReceiveAPIResults(jsonResult)
}

Here you are using GCD to get the main queue and perform the UI update within the block on the main thread.

But be wear, for if you are already on the main thread, calling dispatch_sync(dispatch_get_main_queue()) will wait FOREVER (aka, freezing your app)... so be aware of that.

Luís Cruz
  • 14,780
  • 16
  • 68
  • 100
Ethan
  • 1,567
  • 11
  • 16
  • 1
    You could also use `dispatch_async`, and this will avoid the deadlock. – ahruss Jul 30 '14 at 02:12
  • You mean dispatch_sync? – Ethan Jul 30 '14 at 02:16
  • 1
    No. I mean instead of `dispatch_sync`, use [`dispatch_async`](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/dispatch_async.3.html). Calling `dispatch_sync(dispatch_get_main_queue(), ...)` from the main thread causes a deadlock. `dispatch_async` does not . – ahruss Jul 30 '14 at 02:21
  • Yea but I think his problem is because he's on the background thread, I am 99% sure. – Ethan Jul 30 '14 at 02:27
0

I have a delay problem with segue from a UITableView. I have checked and I appear to be on the main thread. I checked "NSThread.isMainThread()" during prepareForSegue. It always returns true.

I found a solution on Apple Developer forums! https://forums.developer.apple.com/thread/5861

This person says it is a bug in iOS 8.

I followed their suggestion to add a line of code to didSelectRowAtIndexPath...... Despatch_async.....

It worked for me, hopefully you too.

kellz
  • 1
  • 1