Swift 在 API 完成写入数组之前读取和创建数组的速度太快
Swift too fast read and create the array before API finish write to array
我正在练习 DispatchQueue 并整天挣扎,因为 swift 在 API 完成写入数组之前速度太快。我注意到下面两个不同的代码:
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.
这很好,如果 3 秒后等待,我能够从该数组中获取信息。
但是,我认为上面的这段代码是不专业的,因为有时如果 API 很慢并且在 3 秒后还没有准备好,所以我尝试使用专业的方式。这是我的代码:
在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.
两个数组 return 都为零,因为 API 没有完成,我怎样才能让打印等到 API 完成将太阳时间信息插入数组?
更新:这里的代码来自 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()
}
}
您可以像这样更改 "fetchingSunServer" 方法的实现:
func fetchingSunServer(completion: @escaping (() -> Void)) {
//// Your code for fetching the data and after getting the data from server
completion()
}
您的实施将像:
self.fetchingSunServer {
DispatchQueue.main.async {
print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
}
}
在这种情况下您不必使用 DispatchGroup
。
您绝对是在正确的道路上,等待设定的秒数不是一个好的策略。你的代码应该等到获取完成,然后继续,而不是等到 3 秒过去,然后再继续。
在你的第二个例子中,你似乎对缩进和你正在做的事情的顺序有点困惑。
您现在编写代码的方式,基本上是这样说的:
- 转到另一个线程,获取数据。
- 在你这样做的同时,通知主线程你得到的数据。
显然这行不通,因为一个需要发生在另一个之前。
我想你的问题出在这里:
DispatchQueue.main.async {
fetchingSunServer() // Start call the API to get sunrise and sunset time list
etchingSunriseSunset.leave()
} <----
fetchingSunriseSunset.notify(queue: .main) {
print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
}
与通常情况下,下一行的语句在第一行的语句之后处理。你真正在这里做的是第一:
- 排队要完成的操作(异步调用中的所有内容)
- 然后打印
代码仍在 "order" 中发生,但按顺序发生的不是异步块中的内容,而是该块的创建。这可能会让您有些困惑。
我想你真正想要的是更接近于此的东西:
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.")
}
}
这里的区别很小但很重要。
您现在设置异步调用,以便在 fetchingSunServer()
调用结束时进行打印,而不是设置异步调用,然后进行打印。
更好的方法是在闭包中使用 Result
类型。正如所怀疑的那样,您不需要使用 DispatchGroup
,因为您在这里只进行了一次 API 调用。像这样修改你的方法:
Funciton fetchingSunServer 声明:
func fetchingSunServer(completion: @escaping (Result<Response, Error>) -> Void) {
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
DispatchQueue.main.async {
if let data = data {
do {
completion(.success(try JSONDecoder().decode(Response.self, from: data)))
} catch {
completion(.failure(error))
}
}
}
}.resume()
}
}
通话中:
fetchingSunServer { (result) in
switch result {
case .success(let res):
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)]
print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
} else {
print("Error received API from Sunrise and Sunset")
}
case .failure(let error):
print(error)
}
}
我正在练习 DispatchQueue 并整天挣扎,因为 swift 在 API 完成写入数组之前速度太快。我注意到下面两个不同的代码:
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.
这很好,如果 3 秒后等待,我能够从该数组中获取信息。
但是,我认为上面的这段代码是不专业的,因为有时如果 API 很慢并且在 3 秒后还没有准备好,所以我尝试使用专业的方式。这是我的代码:
在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.
两个数组 return 都为零,因为 API 没有完成,我怎样才能让打印等到 API 完成将太阳时间信息插入数组?
更新:这里的代码来自 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()
}
}
您可以像这样更改 "fetchingSunServer" 方法的实现:
func fetchingSunServer(completion: @escaping (() -> Void)) {
//// Your code for fetching the data and after getting the data from server
completion()
}
您的实施将像:
self.fetchingSunServer {
DispatchQueue.main.async {
print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
}
}
在这种情况下您不必使用 DispatchGroup
。
您绝对是在正确的道路上,等待设定的秒数不是一个好的策略。你的代码应该等到获取完成,然后继续,而不是等到 3 秒过去,然后再继续。
在你的第二个例子中,你似乎对缩进和你正在做的事情的顺序有点困惑。
您现在编写代码的方式,基本上是这样说的:
- 转到另一个线程,获取数据。
- 在你这样做的同时,通知主线程你得到的数据。
显然这行不通,因为一个需要发生在另一个之前。
我想你的问题出在这里:
DispatchQueue.main.async {
fetchingSunServer() // Start call the API to get sunrise and sunset time list
etchingSunriseSunset.leave()
} <----
fetchingSunriseSunset.notify(queue: .main) {
print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
}
与通常情况下,下一行的语句在第一行的语句之后处理。你真正在这里做的是第一:
- 排队要完成的操作(异步调用中的所有内容)
- 然后打印
代码仍在 "order" 中发生,但按顺序发生的不是异步块中的内容,而是该块的创建。这可能会让您有些困惑。
我想你真正想要的是更接近于此的东西:
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.")
}
}
这里的区别很小但很重要。
您现在设置异步调用,以便在 fetchingSunServer()
调用结束时进行打印,而不是设置异步调用,然后进行打印。
更好的方法是在闭包中使用 Result
类型。正如所怀疑的那样,您不需要使用 DispatchGroup
,因为您在这里只进行了一次 API 调用。像这样修改你的方法:
Funciton fetchingSunServer 声明:
func fetchingSunServer(completion: @escaping (Result<Response, Error>) -> Void) {
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
DispatchQueue.main.async {
if let data = data {
do {
completion(.success(try JSONDecoder().decode(Response.self, from: data)))
} catch {
completion(.failure(error))
}
}
}
}.resume()
}
}
通话中:
fetchingSunServer { (result) in
switch result {
case .success(let res):
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)]
print("\(sunrisesunsetString.count)\n\(sunrisesunset1970.count)\nFinished create data.")
} else {
print("Error received API from Sunrise and Sunset")
}
case .failure(let error):
print(error)
}
}