我对编程很新,所以我有一个简单的问题。实际上,写作有助于我更快地发现问题。
无论如何,我有一个带有多个异步调用的应用程序,它们嵌套如下:
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
等待完成,然后重新加载我的集合视图。但是,在sortMediaToCategories
我有另一个嵌套函数,它也是异步的并且有一个for循环。
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") })
//....
我无法承受集合视图显然每次重新加载,所以我需要等待循环完成然后重新加载。
我正在尝试使用Dispatch Groups,但正在努力解决它。你能帮帮我吗?任何简单的例子和任何建议都将非常感激。
答案 0 :(得分:5)
您遇到的问题很常见:拥有多个异步任务并等待所有任务完成。
有一些解决方案。最简单的就是使用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()
。 <{1}}和grp.leave
应保持平衡。
enter
最终注册一个闭包,当DispatchGroup leave
平衡时(即其内部计数器达到零),将在指定的调度队列(此处为:main)上调用该闭包。
但是,这个解决方案有一些注意事项:
对于所有这些警告,有很好的解决方案应该使用合适的第三方库来实现。例如,您可以将任务提交给某种&#34;执行者&#34;它控制并发运行的任务数量(匹配OperationQueue和async Operations)。
许多&#34;承诺&#34;或&#34;未来&#34;库简化了错误处理,并且只需一次函数调用就可以帮助您解决这些问题。
答案 1 :(得分:1)
当最后一个项目以这种方式调用成功块时,您可以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") })
}
答案 2 :(得分:1)
您必须在循环内移动group.enter()调用。必须平衡进入和离开的呼叫。如果您成功和失败的mediaToStorageDistribution函数的回调是独占的,您还需要让该组失败。当所有调用enter的块离开时,将调用group notify。并且您可能希望用break替换guard语句中的return,以便跳过缺少URL的项目。现在你从整个sortMediaToCatgories函数返回。
{{1}}