iosswiftasynchronousqueuedispatch-async

Swift too fast read and create the array before API finish write to array


I am practice with DispatchQueue and struggle all day because the swift went too fast before API can finish write to array. What I notice two different codes below:

fetchingSunServer() // Start call the API to get sunrise and sunset time list
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
    print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
})

// Will return the result:
// 4
// 4
// Finished create data.

This is fine, I was able to get information from that array if wait after 3 seconds.

But, I think this code above is unprofessional because sometime what if API is slow and will not be ready after 3 seconds so I am try make professional way. Here my codes:

In ViewController.swift

let fetchingSunriseSunset = DispatchGroup()
   fetchingSunriseSunset.enter()

   DispatchQueue.main.async {
      fetchingSunServer() // Start call the API to get sunrise and sunset time list
      fetchingSunriseSunset.leave()
   }
   fetchingSunriseSunset.notify(queue: .main) {
   print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
}
// Will return the result:
// 0
// 0
// Finished create data.

Both array return zero because the API didn't finish, how can I make print wait until API finish insert sun time information into array?

UPDATED: Here code from fetchingSunServer()

        if let url = URL(string: "https://api.sunrise-sunset.org/json?lat=36.7201600&lng=-4.4203400&formatted=0") {
            URLSession.shared.dataTask(with: url) { data, response, error in
                if let data = data {
                    do {
                        let res = try JSONDecoder().decode(Response.self, from: data)

                        let dateAPI = DateFormatter()
                        dateAPI.locale = Locale(identifier: "en_US_POSIX")
                        dateAPI.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
                        let ss_rise_ast = dateAPI.date(from: res.results.astronomical_twilight_begin)
                        let ss_rise_nau = dateAPI.date(from: res.results.nautical_twilight_begin)
                        let ss_rise_civ = dateAPI.date(from: res.results.civil_twilight_begin)
                        let ss_sunrise = dateAPI.date(from: res.results.sunrise)
                        let ss_solar = dateAPI.date(from: res.results.solar_noon)
                        let ss_sunset = dateAPI.date(from: res.results.sunset)
                        let ss_set_civ = dateAPI.date(from: res.results.civil_twilight_end)
                        let ss_sun_nau = dateAPI.date(from: res.results.nautical_twilight_end)
                        let ss_sun_ast = dateAPI.date(from: res.results.astronomical_twilight_end)

                        let dateToString = DateFormatter()
                        dateToString.dateFormat = "h:mm a"
                        let resultDate_ss_rise_ast = dateToString.string(from: ss_rise_ast!)
                        let resultDate_ss_rise_nau = dateToString.string(from: ss_rise_nau!)
                        let resultDate_ss_rise_civ = dateToString.string(from: ss_rise_civ!)
                        let resultDate_ss_sunrise = dateToString.string(from: ss_sunrise!)
                        let resultDate_ss_solar = dateToString.string(from: ss_solar!)
                        let resultDate_ss_sunset = dateToString.string(from: ss_sunset!)
                        let resultDate_ss_set_civ = dateToString.string(from: ss_set_civ!)
                        let resultDate_ss_sun_nau = dateToString.string(from: ss_sun_nau!)
                        let resultDate_ss_sun_ast = dateToString.string(from: ss_sun_ast!)

                        if res.status == "OK" {
                            self.sunrisesunsetString = [(
                            ss_rise_ast: resultDate_ss_rise_ast,
                            ss_rise_nau: resultDate_ss_rise_nau,
                            ss_rise_civ: resultDate_ss_rise_civ,
                            ss_sunrise: resultDate_ss_sunrise,
                            ss_solar: resultDate_ss_solar,
                            ss_sunset: resultDate_ss_sunset,
                            ss_set_civ: resultDate_ss_set_civ,
                            ss_sun_nau: resultDate_ss_sun_nau,
                            ss_sun_ast: resultDate_ss_sun_ast,
                            ss_timeday: res.results.day_length)]

                            self.sunrisesunset1970 = [(
                            ss_rise_ast: ss_rise_ast!.timeIntervalSince1970,
                            ss_rise_nau: ss_rise_nau!.timeIntervalSince1970,
                            ss_rise_civ: ss_rise_civ!.timeIntervalSince1970,
                            ss_sunrise: ss_sunrise!.timeIntervalSince1970,
                            ss_solar: ss_solar!.timeIntervalSince1970,
                            ss_sunset: ss_sunset!.timeIntervalSince1970,
                            ss_set_civ: ss_set_civ!.timeIntervalSince1970,
                            ss_sun_nau: ss_sun_nau!.timeIntervalSince1970,
                            ss_sun_ast: ss_sun_ast!.timeIntervalSince1970)]
                            self.fetchingSunriseSunset.leave()
                        } else {
                            print("Error received API from Sunrise and Sunset")
                            self.fetchingSunriseSunset.leave()
                        }
                    } catch let error {
                        print(error)
                        self.fetchingSunriseSunset.leave()
                    }
                }
            }.resume()
        }
    }

Solution

  • You can change the implementation of "fetchingSunServer" method a bit like this:

    func fetchingSunServer(completion: @escaping (() -> Void)) {
        //// Your code for fetching the data and after getting the data from server
        completion()
    }
    

    your implementation will be like :

    self.fetchingSunServer {
        DispatchQueue.main.async {
             print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
        }
    }
    

    you don't have to use DispatchGroup in this scenario then.