Swift:OperationQueue - 等待异步调用在下一个开始之前完成(一次最多运行2个URLRequests)

时间:2018-04-07 15:14:45

标签: swift nsurlrequest

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(...)

在较慢的互联网连接上,调用堆栈并立即开始加载每个图像。然后用户必须等待下载彼此“战斗”并且在图像全部一次出现(更多或更少)之前暂时盯着空白屏幕一段时间。如果一个图像出现在另一个图像之后,对用户来说将是一个更好的体验。

我考虑排队这些项目,下载第一个列表,从列表中删除它并递归调用函数,如下所示:

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个请求是浪费时间。让我当前的实现同时处理2个图像将产生大的意大利面条代码,所以我查看了Swift的OperationQueue

我愿意

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2

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

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

1 个答案:

答案 0 :(得分:1)

如果您对iamgeFromUrl进行一次小的更改,则只需要包含操作队列和原始imageFromUrl方法的原始代码。您需要添加几行代码,以确保在下载完成之前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个所需的并发操作。

另请注意,修改了代码以避免使用NSURLNSURLRequest