iPhone Guy
iPhone Guy

Reputation: 1305

Proper usage of dispatch_async (GCD) call

i am trying to load set of images from the server and updating UI with returned images and displaying it by fade animation. it will be repeated forever.

Code snippet:

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
        }

The above one hangs the UI. i have tried to call loadImages in dispatch_async and animation in dispatch_main queue but the issue still persist.

let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_async(dispatch_get_global_queue(priority, 0)) {
 self.loadImages()
}

 dispatch_async(dispatch_get_main_queue(),{
//UIView animation
 })

What is the proper way to handle this.

Thanks

Upvotes: 0

Views: 2345

Answers (3)

Kazuki Sakamoto
Kazuki Sakamoto

Reputation: 14009

Thread-safe problem

Basically, UIKit APIs are not thread-safe. It means UIKit APIs should be called only from the main thread (the main queue). But, actually, there are some exceptions. For instance, UIImage +data: is thread-safe API.

Your code includes some unsafe calls.

UIView.transitionWithView(self.imageView_Promotion, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {

I don't think UIView.transitionWithView is thread-safe.

var image:UIImage = UIImage(named: "test_Image.jpg")!

UIImage +named: is not thread-safe, it uses a global cache or so on. According to UIImage init(named:) Discussion.

You can not assume that this method is thread safe.

Block the main thread

If the thread-safe problem was fixed, it would block the main thread.

Your code calls loadImages method, that downloads an image from the server or loads an image from the file system, from the completion block of UIView.transitionWithView. UIView.transitionWithView should be called from the main thread, so the completion block also will be called from the main thread. It means downloading or loading an image on the main thread. It blocks the main thread.

Example code

Thus, loadImages should be like the following.

Swift Playground code:

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 function

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

// call loadImages() from the main thread

XCPShowView("image", view)
loadImages()
XCPSetExecutionShouldContinueIndefinitely()

Upvotes: 1

Duyen-Hoa
Duyen-Hoa

Reputation: 15804

You have to separate loadImages & update View into 2 GCD thread. Something like that:

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

Upvotes: 0

Arthur Gevorkyan
Arthur Gevorkyan

Reputation: 2107

Your UI freezes as the comletion block for transitionWithView is called on the main thread (a.k.a. UI thread)- that's basically how you end up running "loadImages()" on the main thread. Then, when the load method is being called on the main thread, you create an instance of NSData and initialize it with contents of a URL - this is being done synchronously, therefore your UI is freezing. For what it's worth, just wrap the loadImages() call (the one which is inside the completion block) into a dispatch_async call to a background queue and the freeze should be gone. P.S. I would recommend queuing calls of loadImages() instead of relying on the completion blocks of UI animations.

Upvotes: 0

Related Questions