1

I am trying to get a JSON file from a URL and return the contents using Swift. However, the code fails at the line let httpResponse = response as! NSHTTPURLResponse in the following code. I get an exception at this line and Xcode goes into debug mode.

class func downloadJSONFile()->AnyObject
    {
        let requestURL: NSURL = NSURL(string: "http://www.learnswiftonline.com/Samples/subway.json")!
        let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
        let session = NSURLSession.sharedSession()
        var json:AnyObject = ""
        let task = session.dataTaskWithRequest(urlRequest) {
            (data, response, error) -> Void in

            let httpResponse = response as! NSHTTPURLResponse
            let statusCode = httpResponse.statusCode

            if (statusCode == 200) {

                do{
                    json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)

                }catch {
                    print("Error with Json: \(error)")

                }

            }

        }

        task.resume()

        return json
    }

How can I fix this?

1
  • 1
    You don't return from an asynchronous call. Example here: stackoverflow.com/a/35358750/2227743 Also, you should not force cast the response, it may not be here, so check the error parameter first. Commented Mar 8, 2016 at 20:20

1 Answer 1

5

There are a few issues:

  1. If there is any error in the request, response will be nil, and thus your attempt to force cast it will result in fatal error. Do not use forced unwrapping/casting when dealing with network responses.

  2. There is a deeper problem here that you're trying to return data from a method that runs asynchronously. You should change your method to not return anything, per se, but rather supply a completion handler by which you can asynchronously pass back the relevant data:

     class func downloadJSONFile(completionHandler: @escaping (Any?) -> Void) {
         let requestURL = URL(string: "http://www.learnswiftonline.com/Samples/subway.json")!
         let urlRequest = URLRequest(url: requestURL)
         let session = URLSession.shared
         let task = session.dataTask(with: urlRequest) { data, response, error in
             // check for fundamental networking errors
    
             guard
                 error == nil,
                 let data = data
             else {
                 print(error ?? "Other error")
                 completionHandler(nil)
                 return
             }
    
             guard
                 let httpResponse = response as? HTTPURLResponse,
                 (200 ..< 300) ~= httpResponse.statusCode
             else {
                 print("Invalid status code")
                 completionHandler(nil)
                 return
             }
    
             do {
                 let json = try JSONSerialization.jsonObject(with: data)
                 completionHandler(json)
             } catch let parseError {
                 print("Error parsing: \(parseError)")
                 completionHandler(nil)
             }
         }
    
         task.resume()
     }
    

    and then you call it, using the completion handler (or use trailing closure syntax, like shown below):

     APIClass.downloadJSONFile() { json in
         guard json != nil else {
             print("there was some problem")
             return
         }
    
         // now you can use `json` here
    
         dispatch_async(dispatch_get_main_queue()) {
             // and if you're doing any model or UI updates, dispatch that back to the main queue
         }
     }
    
     // but do not use `json` here, as the above runs asynchronously
    
  3. Note, if you wanted to supply the error information back to the calling routine, you could change it to also return the error information, e.g.:

    enum DownloadError: Error {
        case networkError(Error)
        case notHTTPResponse
        case invalidHTTPResponse(Int)
        case parseError(Error)
    }
    
    class func downloadJSONFile(completionHandler: @escaping (Result<Any, Error>) -> Void) {
        let requestURL = URL(string: "http://www.learnswiftonline.com/Samples/subway.json")!
        let urlRequest = URLRequest(url: requestURL)
        let session = URLSession.shared
        let task = session.dataTask(with: urlRequest) { data, response, error in
            if let error = error {
                completionHandler(.failure(DownloadError.networkError(error)))
                return
            }
    
            guard let httpResponse = response as? HTTPURLResponse, let data = data else {
                completionHandler(.failure(DownloadError.notHTTPResponse))
                return
            }
    
            guard 200 ..< 300 ~= httpResponse.statusCode else {
                completionHandler(.failure(DownloadError.invalidHTTPResponse(httpResponse.statusCode)))
                return
            }
    
            do {
                let json = try JSONSerialization.jsonObject(with: data)
                completionHandler(.success(json))
            } catch let parseError {
                completionHandler(.failure(DownloadError.parseError(parseError)))
            }
        }
    
        task.resume()
    }
    

    And, obviously, the call would change to take both parameters:

    APIClass.downloadJSONFile() { result in
        switch result {
        case .failure(let error):
            print(error)
    
        case .success(let value):
            // and then it would be like before ...
        }
    }
    
  4. When using URLSession in iOS 9 and later, it will not permit cleartext requests (i.e. "http" is not permitted, only "https" is, by default). You can force the app to permit non-https requests by adding the following to your info.plist. See https://stackoverflow.com/a/31254874/1271826 for more information

     <key>NSAppTransportSecurity</key>
     <dict>
         <key>NSExceptionDomains</key>
         <dict>
             <key>learnswiftonline.com</key>
             <dict>
                 <!--Include to allow subdomains-->
                 <key>NSIncludesSubdomains</key>
                 <true/>
                 <!--Include to allow HTTP requests-->
                 <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                 <true/>
                 <!--Include to specify minimum TLS version-->
                 <key>NSTemporaryExceptionMinimumTLSVersion</key>
                 <string>TLSv1.1</string>
             </dict>
         </dict>
     </dict>
    
Sign up to request clarification or add additional context in comments.

1 Comment

FWIW, I've updated the above to use contemporary Swift syntax, but I wouldn't generally use JSONSerialization and Any as types. We'd generally use JSONDecoder nowadays, but it seems like it's beyond the scope of this question...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.