1
votes

I've been working on an app for watchOS 2, and am led to believe that my function, which uses NSData(contentsOfUrl) to download some JSON data, may be too heavy, causing the app to crash. My research has led me to believe that NSURLSession is the way to go, but I'm having trouble figuring out how to convert my function so the results and returned in the same way.

In my app, I have a line let data = myData(). This calls myData.swift;

class myData {

   var datas = myData.fetchData()

   class func fetchData() -> [myData] {

        let myURL = "http://www.url.com/json/"

        let dataURL = NSURL(string: myURL)

        var optData:NSData? = nil

        do {
            optData = try NSData(contentsOfURL: dataURL!, options: NSDataReadingOptions.DataReadingMappedIfSafe)
        }
        catch {
            print("Handle \(error) here.")
        }

        var datas = [myData]()

        if let data = optData {

            do {

                let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! NSArray

                for item in json {
                    //Do JSON stuff and 
                    //Append to datas array
                    datas.append(dataSet)
                }
            } catch {
                print(error)
            }
        }
        return datas
    }
  }
}

This code has been slightly truncated, but it works without issue in the Simulator. On the device, however, when this code is called, it crashes.

This is my attempt at converting myData.swift to a NSURLSession;

class myData {

    class func fetchData() -> [myData] {

        var task: NSURLSessionDataTask?

        let myURL = "http://www.url.com/json/"

        let dataURL = NSURL(string: myURL)

        let conf = NSURLSessionConfiguration.defaultSessionConfiguration()

        let session = NSURLSession(configuration: conf)

        task = session.dataTaskWithURL(dataURL!) { (data, res, error) in
            if let e = error {
                print("dataTaskWithURL fail: \(e.debugDescription)")
                return
            }


            var datas = [myData]()

            do {

                let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSArray

                for item in json {
                    //Do JSON stuff
                    //Append to datas array

                    datas.append(dataSet)
                }
            } catch {
                print(error)
            }
            return datas
            //Unexpected return value in non-void function
        }
    }
}

Would it be possible for someone to advise how I could successfully eradicate my error, and how I would still be able to call let data = myData() and have it return the same way NSData(contentsofUrl) does. I do understand that NSURLSession is going to require a completion handler or something because of it running on the same thread, but I can't figure it out! Thanks!

1

1 Answers

2
votes

I'm afraid you cannot do what you describe. The contentsOfURL method does not return until the entire file is transferred. If you have a slow connection or the file is large, the UI would appear to freeze as the main thread is busy. Only use that method for local files.

Looks like your second attempt has two issues:

First, for security reasons, you can only load secure (https:) URLs by default. See this note: https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/

Second, dataTaskWithURL does not initiate the transfer; the dataTask won't do anything until you instruct it to start by calling task.resume(). After all the data has been transferred, the closure code will be executed by a different thread. This is why you cannot return a value.

You can still create your datas array, but you need to move the declaration to the top level of your class. Remember the closure is executed by a different thread, so you will need to reference the array as self.datas from within the closure.

If you need to update UI elements after the data has been loaded in the closure, you need to issue the update via the main thread. To force a table to display with the new data, you would add this to the end of the closure:
self.myTable.performSelectorOnMainThread( "reloadData", withObject: nil, waitUntilDone: false)