Swift: OperationQueue - дождитесь завершения асинхронных вызовов, прежде чем начинать следующий (не более 2 одновременно выполняемых 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(...)
,
При более медленном интернет-соединении звонки складываются, и он начинает загружать каждое изображение сразу. Затем пользователь должен подождать, пока загрузки "сразятся" друг с другом, и некоторое время смотрит на пустой экран, прежде чем все изображения появляются сразу (более или менее). Для пользователя было бы намного лучше, если бы одно изображение появлялось за другим.
Я думал о постановке в очередь элементов, загрузке первого из списка, удалении его из списка и рекурсивном вызове функции следующим образом:
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 ответ
Ваш оригинальный код с очередью операций и ваш оригинальный 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 требуемые одновременные операции.
Также обратите внимание, что код изменен, чтобы избежать использования NSURL
а также NSURLRequest
,