1

My app needs to be able to decode multiple JSON files (around 50) one at a time. Here is the code I currently use to do this:

class Api {
    static var Acceptable: Bool = true //Acceptable range to reload sites
    static var order = 1
    
    //From: https://stackoverflow.com/questions/38469648/unexpected-non-void-return-value-in-void-function-in-new-swift-class
    //Also from: https://stackoverflow.com/questions/65999965/how-to-pass-arguments-into-a-function-with-completion-swift (input part)
    func CoordsUpdate(completion: @escaping ((Double, Double, String) -> Void)) {
        var states = [ "AK", "AL", "AR", "AS", "AZ", "CA", "CO", "CT", "DC", "DE", "FL", "GA", "GU", "HI", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MA", "MD", "ME", "MI", "MN", "MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", "NM", "NV", "NY", "OH", "OK", "OR", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VA", "VI", "VT", "WA", "WI", "WV", "WY"]

        for state in states {
            let URL = String("https://waterservices.usgs.gov/nwis/iv/?format=json&indent=off&stateCd=\(state)&parameterCd=00065&siteStatus=active")
            print("states URL:", URL)
            Api().getCoordinates(inputurl: URL) { lat, long, name in
                completion(lat, long, name)
                return
            }
            
            repeat {
                print("waiting for processing", state)
                //sleep(UInt32(0.1)) //waits for 0.1 sec
                sleep(1)
            } while (Api.order < 1)
            
            print("coordsupdate done for:", state)
            states.removeFirst()
            print("states left to process:", states)
        }
        print("done buh")
    }
    
    func getCoordinates(inputurl: String, completion: @escaping ((Double, Double, String) -> Void)) {
        Api.order = 0
        print("order is now 0")
        print("input:", inputurl)
        let url = URL(string: inputurl)!
        print("Fetching data")
        //Timer from: https://stackoverflow.com/questions/24755558/measure-elapsed-time-in-swift
        let start = DispatchTime.now()
        //Error handling from: https://www.hackingwithswift.com/forums/swift/urlsession-error/780
        URLSession.shared.dataTask(with: url) { [self] data, response, error in
            if let data = data {
                print("Data fetched", url)
                print("data size:",data)
                let end = DispatchTime.now()
                let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // <<<<< Difference in nano seconds (UInt64)
                var timeInterval = Double(nanoTime) / 1_000_000_000 // Technically could overflow for long running tests
                timeInterval = ((round(timeInterval * 100)) / 100) //Rounds to 2 decimals
                print("Time to fetch data: \(timeInterval) seconds")

                let posts = try! JSONDecoder().decode(Post.self, from: data)
                print("JSON processed:", url)
                
                let postarray = (posts.value?.timeSeries) //all sites in an array (not including header/query info)
                //from https://stackoverflow.com/questions/63609283/swift-for-in-loop-requires-deepspeechtokenmetadata-to-conform-to-sequence (or not idk)
                var numsadded = 0
                var numsremoved = 0
                for item in (postarray!) {
                    //From: https://stackoverflow.com/questions/42523769/how-to-parse-json-data-and-eliminate-duplicates-in-swift
                    for _ in nome.names {
                        let name = (item.sourceInfo?.siteName)
//                        print("nome:", name!)
                        if nome.names.contains(name!) {
//                            print("duplicate")
                            numsremoved += 1
                            break
                        } else {
                            nome.names.append(name!)
                            //print("all names:", nome.names)
//                            print("not duplicate")
                            numsadded += 1
                            let lat = (item.sourceInfo?.geoLocation?.geogLocation?.latitude)
                            let long = (item.sourceInfo?.geoLocation?.geogLocation?.longitude)
                            completion(lat!, long!, name!)
                        }
                    }
                }
                Api.order = 1
                print("order is now 1")
                return
            }
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
        }.resume()
        print("done with urlsession")
    }
}

However this code crashes when handling any more than a few JSON files/API requests, giving me the error Thread 10: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))) at the line let posts = try! JSONDecoder().decode(Post.self, from: data)

As it stands right now in SwiftUI how would I go about doing this?

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 1
    In SwiftUI the most efficient (and reliable) way is using `Combine` otherwise `DispatchGroup`. – vadian Jul 13 '21 at 19:23
  • But this is not about design or what way to execute the decoding, you have a plain decoding error caused by invalid json as the error clearly states. So first you need to investigate and fix that error before refactoring your code. But do you really want your app to crash just because of an error, wouldn't `try` inside a `do/catch` be a better solution? – Joakim Danielson Jul 13 '21 at 20:09
  • You say "in SwiftUI" -- this doesn't have anything to do with SwiftUI. It's just a Swift issue. The error is telling you your JSON isn't valid. The only way to diagnose that is to see the JSON. – jnpdx Jul 13 '21 at 21:54
  • If you are repeatedly calling `URLSession.shared`, that's not a right approach. That's a crash-my-app approach. – El Tomato Jul 13 '21 at 21:58
  • What's the alternative? – Colin Miller Jul 15 '21 at 16:46

0 Answers0