Правильное использование вызова dispatch_async (GCD)
Я пытаюсь загрузить набор изображений с сервера и обновлять пользовательский интерфейс с возвращенными изображениями и отображать его с помощью затухающей анимации. это будет повторяться вечно.
Фрагмент кода:
override func viewDidLoad(animated: Bool) {
super.viewDidLoad(animated)
self.loadImages()
}
func loadImages(){
var urlImage:UIImage?
//self.array_images contains preloaded images (not UIImage) objects which i get from different api call.
if imageCount >= self.array_images!.count{
imageCount = 0
}
var img = self.array_images[imageCount]
var url = NSURL(string: img.url!)
var data = NSData(contentsOfURL: url!)
if (data != nil){
urlImage = UIImage(data: data!)
UIView.transitionWithView(self.imageView_Promotion, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
println("Begin Animation")
self.imageView_Promotion.image = realImage
}, completion:{ finished in
println("Completed")
self.imageCount++
self.loadImages() //another issue: it calls the function more than couple of times
})
}
}else{
var image:UIImage = UIImage(named: "test_Image.jpg")!
imageView_Promotion.image = image
}
Выше один висит пользовательский интерфейс. я пытался вызвать loadImages в dispatch_async и анимацию в очереди dispatch_main, но проблема все еще сохраняется.
let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_async(dispatch_get_global_queue(priority, 0)) {
self.loadImages()
}
dispatch_async(dispatch_get_main_queue(),{
//UIView animation
})
Какой правильный способ справиться с этим.
Спасибо
3 ответа
Потокобезопасная проблема
По сути, UIKit API не являются потокобезопасными. Это означает, что API UIKit следует вызывать только из основного потока (основной очереди). Но, на самом деле, есть некоторые исключения. Например, UIImage +data: является поточно-ориентированным API.
Ваш код включает в себя некоторые небезопасные звонки.
UIView.transitionWithView(self.imageView_Promotion, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
Я не думаю, что UIView.transitionWithView является потокобезопасным.
var image:UIImage = UIImage(named: "test_Image.jpg")!
UIImage + named: не является поточно-ориентированным, он использует глобальный кеш и т. Д. По словам UIImage init(по имени:) Обсуждение.
Вы не можете предполагать, что этот метод является потокобезопасным.
Заблокировать основной поток
Если бы проблема с поточностью была исправлена, она блокировала бы основной поток.
Ваш код вызывает метод loadImages, который загружает изображение с сервера или загружает изображение из файловой системы из блока завершения UIView.transitionWithView. UIView.transitionWithView должен вызываться из основного потока, поэтому блок завершения также будет вызываться из основного потока. Это означает загрузку или загрузку изображения в главном потоке. Он блокирует основной поток.
Пример кода
Таким образом, loadImages должен выглядеть следующим образом.
Код Swift Playground:
import UIKit
import XCPlayground
func loadImage(file:String) -> UIImage {
let path = NSBundle.mainBundle().pathForResource(file, ofType: "")
let data = NSData(contentsOfFile: path!)
let image = UIImage(data: data!)
return image!
}
var index = 0
let files = ["image0.png", "image1.png", "image2.png"]
let placementImage = loadImage(files[0])
let view = UIImageView(image:placementImage)
// функция loadImages
func loadImages() {
let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_async(dispatch_get_global_queue(priority, 0)) {
/*
* This block will be invoked on the background thread
* Load an image on the background thread
* Don't use non-background-thread-safe APIs like UIImage +named:
*/
if index >= files.count {
index = 0
}
var file = files[index++]
let image = loadImage(file)
dispatch_async(dispatch_get_main_queue()) {
/*
* This block will be invoked on the main thread
* It is safe to call any UIKit APIs
*/
UIView.transitionWithView(view, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
/*
* This block will be invoked on the main thread
* It is safe to call any UIKit APIs
*/
view.image = image
}, completion:{ finished in
/*
* This block will be invoked on the main thread
* It is safe to call any UIKit APIs
*/
loadImages()
})
}
}
}
// вызвать loadImages() из основного потока
XCPShowView("image", view)
loadImages()
XCPSetExecutionShouldContinueIndefinitely()
Ваш пользовательский интерфейс останавливается, так как блок завершения для transitionWithView вызывается в главном потоке (он же поток пользовательского интерфейса)- именно так вы в конечном итоге запускаете "loadImages()" в основном потоке. Затем, когда метод загрузки вызывается в главном потоке, вы создаете экземпляр NSData и инициализируете его содержимым URL -адреса - это выполняется синхронно, поэтому ваш пользовательский интерфейс зависает. Для этого просто оберните вызов loadImages () (тот, что находится внутри блока завершения) в вызов dispatch_async в фоновую очередь, и остановка должна быть устранена. PS Я бы рекомендовал ставить в очередь вызовы loadImages () вместо того, чтобы полагаться на блоки завершения анимаций пользовательского интерфейса.
Вы должны разделить loadImages и обновить View в потоке 2 GCD. Что-то вроде того:
override func viewDidLoad(animated: Bool) {
super.viewDidLoad(animated)
dispatch_async(dispatch_queue_create("com.company.queue.downloadImages", nil {
() -> Void in
self.loadImages()
}))
}
/**
Call this function in a GCD queue
*/
func loadImages() {
... first, download image
... you have to add stop condition cause I see that self.loadImages is called recursively in 'completion' clause below
... then, update UIView if data != nil
if (data != nil) {
dispatch_async(dispatch_get_main_queue(),nil, {
() -> Void in
UIView.transitionWithView.... {
}, completion: { finished in
dispatch_async(dispatch_queue_create("com.company.queue.downloadImages", nil, {
() -> Void in
self.imageCount++
self.loadImages()
}))
})
})
}
}