Swift:OperationQueue-等待异步调用完成,然后再开始下一步(一次最多运行2个URL请求)

Swift:OperationQueue-等待异步调用完成,然后再开始下一步(一次最多运行2个URL请求),swift,nsurlrequest,Swift,Nsurlrequest,tl;dr我有一个OperationQueue,我想同时运行两个操作。这些操作异步下载一些东西,因此它们都会立即被触发,而不是一个接一个地运行 我通过对每个图像执行以下操作来填充非常大的图像表: public func imageFromUrl(_ urlString: String) { if let url = NSURL(string: urlString) { let request = NSURLRequest(url: url as URL)

tl;dr我有一个OperationQueue,我想同时运行两个操作。这些操作异步下载一些东西,因此它们都会立即被触发,而不是一个接一个地运行

我通过对每个图像执行以下操作来填充非常大的图像表:

public func imageFromUrl(_ urlString: String) {
    if let url = NSURL(string: urlString) {
        let request = NSURLRequest(url: url as URL)
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)

        let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
            if let imageData = data as Data? {
                DispatchQueue.main.async {
                    self.setImageData(imageData)
                }
            }
        });

        task.resume()
    }
imageView.imageFromUrl(…)
那样调用它

在较慢的internet连接上,调用堆栈会立即开始加载每个图像。然后,用户必须等待下载彼此“战斗”,并在一次(或多或少)显示所有图像之前盯着一个空白屏幕看一段时间。如果一幅图像一幅接一幅地出现,对用户来说将是一种更好的体验

我考虑对项目进行排队,下载列表中的第一个项目,将其从列表中删除,然后像这样递归调用函数:

func doImageQueue(){

    let queueItem = imageQueue[0]

    if let url = NSURL(string: (queueItem.url)) {
        print("if let url")
        let request = NSURLRequest(url: url as URL)
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)

        let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
            print("in")
            if let imageData = data as Data? {
                print("if let data")
                DispatchQueue.main.async {
                    queueItem.imageView.setImageData(imageData)
                    self.imageQueue.remove(at: 0)
                    if(self.imageQueue.count>0) {
                        self.doImageQueue()
                    }
                }
            }
        });

        task.resume()
    }
}
这会一个接一个地加载图像,我认为一次不运行至少两个请求是浪费时间。让我当前的实现同时处理2个图像将导致大量的意大利面代码,因此我研究了Swift的
OperationQueue

我愿意

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2

for (all images) {
    queue.addOperation {
        imageView.imageFromUrl(imageURL
    }
}

但这也会同时触发所有调用,可能是因为请求异步运行,并且方法调用在等待下载映像之前结束。我该怎么处理呢?该应用程序也将在watchOS上运行,也许已经有了一个库,但我认为如果没有库的话,这应该不会太难实现。缓存不是一个问题。

如果对
imageFromUrl
进行一个小更改,您只需要使用操作队列的原始代码和原始
iamgeFromUrl
方法。您需要添加几行代码,以确保
imageFromUrl
在下载完成之前不会返回

这可以使用信号量来完成

public func imageFromUrl(_ urlString: String) {
    if let url = URL(string: urlString) {
        let request = URLRequest(url: url)
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)

        let semaphore = DispatchSemaphore(value: 0)
        let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
            semaphore.signal()
            if let imageData = data as Data? {
                DispatchQueue.main.async {
                    self.setImageData(imageData)
                }
            }
        });

        task.resume()
        semaphore.wait()
    }
}
如前所述,
imageFromUrl
仅在下载完成后返回。这现在允许操作队列正确运行2个所需的并发操作


另请注意,修改代码是为了避免使用
NSURL
NSURLRequest

您正在处理异步任务,因此您需要使它们与信号量或类似对象同步(如下rmaddy所示)或者您需要创建一个异步自定义
操作
子类,如或中所述。完全不相关,但是为什么要创建
NSURL
NSURLRequest
,只将它们转换为本机
URL
URLRequest
对象?从一开始就创建这些对象。不要在你的代码中使用那些
NS
类。Rob你说得对,复制了这段代码并添加了我的代码,因为我更关心的是让它在第一时间工作,但如果没有你,我不会注意到,defo会改变它吗。谢谢你引起我的注意,我很欣赏这是正确的答案,你太棒了,谢谢!!!完美的解决方案,不是完美的!当同时下载多个图像时,可能会导致问题(生成的线程太多)。