Search code examples
iosjsonswiftrestuikit

How to define a fallback case if a remote GET request fails?


I recently started with iOS development, and I'm currently working on adding new functionality to an existing app. For this feature I need to obtain a JSON file from a web server. However, if the server is unreachable (no internet/server unavailable/etc), a local JSON needs to be used instead.

In my current implementation I tried using a do catch block, but if there's no internet connection, the app just hangs instead of going to the catch block. JSON parsing and local data reading seem to work fine, the problem is likely in the GET method, as I tried to define a callback to return the JSON data as a separate variable, but I'm not sure if that's the correct way.

What is the best way to handle this scenario?

 let url = URL(string: "https://jsontestlocation.com")  // test JSON

        do {
            // make a get request, get the result as a callback
            let _: () = getRemoteJson(requestUrl: url!, requestType: "GET") {
                remoteJson in
                performOnMainThread {
                    self.delegate.value?.didReceiveJson(.success(self.parseJson(jsonData: remoteJson!)!))
                }
            }

        }
        catch {
            let localFile = readLocalFile(forName: "local_json_file")
            let localJson = parseJson(jsonData: localFile!)
            if let localJson = localJson {
                self.delegate.value?.didReceiveJson(.success(localJson))
            }
        }

getRemoteJson() implementation:

private func getRemoteJson(requestUrl: URL, requestType: String, completion: @escaping (Data?) -> Void) {
        // Method which returns a JSON questionnaire from a remote API
        
        var request = URLRequest(url: requestUrl)  // create the request
        request.httpMethod = requestType
        
        // make the request
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            
            // check if there is any error
            if let error = error {
                print("GET request error: \(error)")
            }
            
            // print the HTTP response
            if let response = response as? HTTPURLResponse {
                print("GET request status code: \(response.statusCode)")
            }
            
            guard let data = data else {return}  // return nil if no data
            
            completion(data)  // return
        }
        task.resume()  // resumes the task, if suspended
    }

parseJson() implementation:

private func parseJson(jsonData: Data) -> JsonType? {
        // Method definition
        do {
            let decodedData = try JSONDecoder().decode(JsonType.self, from: jsonData)
            return decodedData
            
        } catch {
            print(error)
        }
        
        return nil
    }

Solution

  • If you don't have to use complex logic with reachability, error handling, request retry etc. just return nil in your completion in case of data task, HTTP and No data errors:

    func getRemoteJson(requestUrl: URL, requestType: String, completion: @escaping (Data?) -> Void) {
        var request = URLRequest(url: requestUrl)
        request.httpMethod = requestType
        
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            // Task error
            guard  error == nil else {
                print("GET request error: \(error!)")
                completion(nil)
                return
            }
            
            // HTTP error
            guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                print("GET request failed: \(response!.description)")
                completion(nil)
                return
            }
            
            // No data
            guard let data = data else {
                completion(nil)
                return
            }
            
            completion(data)
        }
        task.resume()
    }
    
    let url = URL(string: "https://jsontestlocation.com")!
    getRemoteJson(requestUrl: url, requestType: "GET") { remoteJson in
        if let json = remoteJson {
            print(json)
            ...
        }
        else {
            print("Request failed")
            ...
        }
    }