Swift 中的嵌套异步调用
Nested async calls in Swift
总的来说,我是编程新手,所以我有这个可能很简单的问题。实际上,写作可以帮助我更快地发现问题。
无论如何,我有一个带有多个异步调用的应用程序,它们是这样嵌套的:
InstagramUnoficialAPI.shared.getUserId(from: username, success: { (userId) in
InstagramUnoficialAPI.shared.fetchRecentMedia(from: userId, success: { (data) in
InstagramUnoficialAPI.shared.parseMediaJSON(from: data, success: { (media) in
guard let items = media.items else { return }
self.sortMediaToCategories(media: items, success: {
print("success")
// Error Handlers
看起来很糟糕,但这不是重点。一旦我开始工作,我将调查 Promise Kit。
我需要 sortMediaToCategories
等待完成,然后重新加载我的 collection 视图。但是,在 sortMediaToCategories
中我有另一个嵌套函数,它也是异步的并且有一个 for in 循环。
func sortMediaToCategories(media items: [StoryData.Items],
success: @escaping (() -> Swift.Void),
failure: @escaping (() -> Swift.Void)) {
let group = DispatchGroup()
group.enter()
for item in items {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: { group.notify(queue: .global(), execute: {
self.collectionView.reloadData()
group.leave()
}) },
failure: { print("error") })
//....
显然我负担不起每次都重新加载 collection 视图,所以我需要等待循环完成然后重新加载。
我正在尝试使用 Dispatch Groups,但遇到了困难。你能帮我解决这个问题吗?任何简单的例子和任何建议都将不胜感激。
这样就可以reloadData
当最后一项调用成功块时
let lastItemIndex = items.count - 1
for(index, item) in items.enumerated() {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: {
if index == lastItemIndex {
DispatchQueue.global().async {
self.collectionView.reloadData()
}
}
},
failure: { print("error") })
}
您面临的问题很常见:有多个异步任务并等待所有任务完成。
有几个解决方案。最简单的是利用 DispatchGroup
:
func loadUrls(urls: [URL], completion: @escaping ()->()) {
let grp = DispatchGroup()
urls.forEach { (url) in
grp.enter()
URLSession.shared.dataTask(with: url) { data, response, error in
// handle error
// handle response
grp.leave()
}.resume()
}
grp.notify(queue: DispatchQueue.main) {
completion()
}
}
函数 loadUrls
是异步的,需要一个 URL 数组作为输入和一个完成处理程序,当所有任务完成时将调用该处理程序。这将通过 DispatchGroup
来完成,如图所示。
关键是,确保 grp.enter()
在 调用任务之前被调用, grp.leave
在任务 调用时被调用已完成。 enter
和 leave
应该是平衡的。
grp.notify
最终注册了一个闭包,当 DispatchGroup grp
平衡(即其内部计数器达到零)时,将在指定的调度队列(此处:main)上调用该闭包。
不过,此解决方案有一些注意事项:
- 所有任务将几乎同时启动并且运行并发
- 此处未显示通过完成处理程序报告所有任务的最终结果。它的实施将需要适当的同步。
对于所有这些注意事项,都有很好的解决方案,应该利用合适的第三方库来实施。例如,您可以将任务提交给某种 "executer" 类型,它控制同时有多少任务 运行 (匹配 OperationQueue 和异步操作)。
许多 "Promise" 或 "Future" 库简化了错误处理,还可以帮助您通过一次函数调用解决此类问题。
您必须将 group.enter() 调用移动到循环内。进入和离开的呼叫必须平衡。如果您对成功和失败的 mediaToStorageDistribution 函数的回调是独占的,您还需要在失败时离开该组。当所有调用 enter 的块离开组时,通知将被调用。你可能想用 break 替换 guard 语句中的 return ,以跳过缺少 URL 的项目。现在您正在从整个 sortMediaToCatgories 函数中 returning。
func sortMediaToCategories(media items: [StoryData.Items], success: @escaping (() -> Void), failure: @escaping (() -> Void)) {
let group = DispatchGroup()
for item in items {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else { break }
group.enter()
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: { group.leave() },
failure: {
print("error")
group.leave()
})
}
}
group.notify(queue: .main) {
self.collectionView.reloadData()
}
}
总的来说,我是编程新手,所以我有这个可能很简单的问题。实际上,写作可以帮助我更快地发现问题。
无论如何,我有一个带有多个异步调用的应用程序,它们是这样嵌套的:
InstagramUnoficialAPI.shared.getUserId(from: username, success: { (userId) in
InstagramUnoficialAPI.shared.fetchRecentMedia(from: userId, success: { (data) in
InstagramUnoficialAPI.shared.parseMediaJSON(from: data, success: { (media) in
guard let items = media.items else { return }
self.sortMediaToCategories(media: items, success: {
print("success")
// Error Handlers
看起来很糟糕,但这不是重点。一旦我开始工作,我将调查 Promise Kit。
我需要 sortMediaToCategories
等待完成,然后重新加载我的 collection 视图。但是,在 sortMediaToCategories
中我有另一个嵌套函数,它也是异步的并且有一个 for in 循环。
func sortMediaToCategories(media items: [StoryData.Items],
success: @escaping (() -> Swift.Void),
failure: @escaping (() -> Swift.Void)) {
let group = DispatchGroup()
group.enter()
for item in items {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: { group.notify(queue: .global(), execute: {
self.collectionView.reloadData()
group.leave()
}) },
failure: { print("error") })
//....
显然我负担不起每次都重新加载 collection 视图,所以我需要等待循环完成然后重新加载。
我正在尝试使用 Dispatch Groups,但遇到了困难。你能帮我解决这个问题吗?任何简单的例子和任何建议都将不胜感激。
这样就可以reloadData
当最后一项调用成功块时
let lastItemIndex = items.count - 1
for(index, item) in items.enumerated() {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: {
if index == lastItemIndex {
DispatchQueue.global().async {
self.collectionView.reloadData()
}
}
},
failure: { print("error") })
}
您面临的问题很常见:有多个异步任务并等待所有任务完成。
有几个解决方案。最简单的是利用 DispatchGroup
:
func loadUrls(urls: [URL], completion: @escaping ()->()) {
let grp = DispatchGroup()
urls.forEach { (url) in
grp.enter()
URLSession.shared.dataTask(with: url) { data, response, error in
// handle error
// handle response
grp.leave()
}.resume()
}
grp.notify(queue: DispatchQueue.main) {
completion()
}
}
函数 loadUrls
是异步的,需要一个 URL 数组作为输入和一个完成处理程序,当所有任务完成时将调用该处理程序。这将通过 DispatchGroup
来完成,如图所示。
关键是,确保 grp.enter()
在 调用任务之前被调用, grp.leave
在任务 调用时被调用已完成。 enter
和 leave
应该是平衡的。
grp.notify
最终注册了一个闭包,当 DispatchGroup grp
平衡(即其内部计数器达到零)时,将在指定的调度队列(此处:main)上调用该闭包。
不过,此解决方案有一些注意事项:
- 所有任务将几乎同时启动并且运行并发
- 此处未显示通过完成处理程序报告所有任务的最终结果。它的实施将需要适当的同步。
对于所有这些注意事项,都有很好的解决方案,应该利用合适的第三方库来实施。例如,您可以将任务提交给某种 "executer" 类型,它控制同时有多少任务 运行 (匹配 OperationQueue 和异步操作)。
许多 "Promise" 或 "Future" 库简化了错误处理,还可以帮助您通过一次函数调用解决此类问题。
您必须将 group.enter() 调用移动到循环内。进入和离开的呼叫必须平衡。如果您对成功和失败的 mediaToStorageDistribution 函数的回调是独占的,您还需要在失败时离开该组。当所有调用 enter 的块离开组时,通知将被调用。你可能想用 break 替换 guard 语句中的 return ,以跳过缺少 URL 的项目。现在您正在从整个 sortMediaToCatgories 函数中 returning。
func sortMediaToCategories(media items: [StoryData.Items], success: @escaping (() -> Void), failure: @escaping (() -> Void)) {
let group = DispatchGroup()
for item in items {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else { break }
group.enter()
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: { group.leave() },
failure: {
print("error")
group.leave()
})
}
}
group.notify(queue: .main) {
self.collectionView.reloadData()
}
}