在 Swift 上发出多个网络请求

Posted

技术标签:

【中文标题】在 Swift 上发出多个网络请求【英文标题】:Make multiple network requests on Swift 【发布时间】:2020-06-18 00:11:46 【问题描述】:

我正在尝试从学校课程目录网站下载数据。我在变量 UrlBook 中有 64 个 Url。我已经成功编写了代码来下载一系列课程,并使用完成处理程序方法将它们从单个 url 转换为单个 subject 对象。我真的不知道我应该如何实现从 64 个 url 收集所有主题并最终将它们变成 catalog 对象(它包含主题对象列表)。

我已经阅读了很多关于异步和同步处理的文章和帖子,这让我很困惑。我非常感谢简单直接的代码来帮助我解决这个问题。谢谢各位!

let urlBook = getUrlFromBook()

func fetchClassInfo(url:URL,completion: @escaping ([clase])-> Void)
    let task = URLSession.shared.dataTask(with: url)(data, response, error) in
        let jsonDecoder = JSONDecoder()
        if let data = data,
            let collection:[clase] = try?  jsonDecoder.decode([clase].self, from: data)
            completion(collection)
        else
            print("Either no data was returned, or data was not properly decoded.")
            //completion(nil)
        
    
    task.resume()


fetchClassInfo(url:urlBook.book[0])(clase) in
    let newSubject = makeNewSubject(subjectIndex: 0, collectionOfCourse: clase)
    var masterCatalog = catalog(subjectCollection: [])
     masterCatalog.addSubject(newSubject: newSubject)

    

【问题讨论】:

所以让我说得对,你有一堆 url,你想向所有这些 url 发出多个请求? 您还希望网络请求一个接一个地并发(即所有在不同线程中并行)或串行方式? 是的,我收集了它们,我尝试访问所有它们,然后将所有数据打包到我的应用程序的一个巨大对象(称为目录)中。对于它是否应该是没有偏好并发与否。我只是对使用闭包和完成处理程序感到沮丧。我的第一次尝试是将 fetchClassInfo 闭包包装到一个 for 循环中以发出所有这些网络请求,但结果它失败了。 我希望获得一些关于如何访问多个网络并将返回的对象(主题)添加到列表中的清晰结构和见解,然后创建一个名为 catalog 的新对象,其中包含一个字段“var subjectCollection :[主题]" 【参考方案1】:

您基本上可以创建如下所示的逻辑。这个函数接受一个 url 列表并返回一个完整的 Subject 列表。您可以根据需要修改模型等。在这个函数中; DispatchGroup 是在调用completion 之前等待所有请求完成,DispatchQueue 是为了防止在将主题附加到数组时出现“数据竞争”。

func downloadUrls(urls: [URL], completion: @escaping ([Subject]) -> Void) 
    var subjectCollection: [Subject] = []    
    let urlDownloadQueue = DispatchQueue(label: "com.urlDownloader.urlqueue")
    let urlDownloadGroup = DispatchGroup()

    urls.forEach  (url) in
        urlDownloadGroup.enter()

        URLSession.shared.dataTask(with: url, completionHandler:  (data, response, error) in
            guard let data = data, 
                let subject = try? JSONDecoder().decode(Subject.self, from: data) else                     
                // handle error                    
                urlDownloadQueue.async 
                    urlDownloadGroup.leave()
                
                return
            

            urlDownloadQueue.async 
                subjectCollection.append(subject)
                urlDownloadGroup.leave()
            
        )
    

    urlDownloadGroup.notify(queue: DispatchQueue.global()) 
        completion(subjectCollection)
    

【讨论】:

【参考方案2】:

我建议查看 Promises 方法,因为它有现成的异步任务解决方案。 Swift 上有很多 Promises 的实现。更多你可以尝试使用 SDK 的 Combine 框架,但它有点复杂,并且仅适用于 ios 13。

例如,我的PromiseQ swift 包中有一个示例:

Promise.all( paths.map  fetch($0)  ) // Download all paths concurrently
.then  dataArray in
    // Parse all data to array of results
    let results:[YourClass] = dataArray.compactMap  try? JSONDecoder().decode(YourClass.self, from: $0) 

地点:

paths:[String] - 包含要下载的 http 路径的数组。 fetch(_ path: String) -> Promise<Data> - 通过返回承诺的路径下载数据的特殊函数。 YourClass - 要解析的类。

【讨论】:

【参考方案3】:

这是一个同时下载所有url的函数:

func downloadAllUrls(urls: [String])
    let dispatchGroup = DispatchGroup()
    for url in urls 
        dispatchGroup.enter()
        // Here is where you have to do your get call to server 
        // and when finished call dispatchGroup.leave()
    
    dispatchGroup.notify(queue: .main) 
        // Do what ever you want after all calls are finished
    

它使用 DispatchQueue 来通知所有请求何时完成,然后您可以对它们做任何您想做的事情。服务器可能是您对网络的自定义异步调用。我的也是这样。

【讨论】:

如果您想知道如何实现我使用 @Jay Gatsby 的 Server.get 调用,请告诉我

以上是关于在 Swift 上发出多个网络请求的主要内容,如果未能解决你的问题,请参考以下文章

如何调试网络请求 Swift

Swift 同步网络请求

使用 Swift 和 Combine 链接 + 压缩多个网络请求

Swift:链接多个网络请求,Alamofire

Swift GCD之解决多个网络请求的尴尬

为啥 GuzzleHttp 客户端在 Laravel/Lumen 上使用它发出网络请求时会抛出 ClientException?