Search code examples
swiftanimationloadingbackground-thread

Swift: Running code prevents other code until completed


I am calling an animation in ViewDidAppear, and then a bit later in ViewDidAppear I am calling a function which creates several arrays of UIImage objects and takes 4-5 seconds to complete.

The animation meanwhile, is only 0.8 seconds long, and has a completion block:

UIView.animate(withDuration: 0.8, delay: 0.0, options: UIViewAnimationOptions(), animations: {
            self.myView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
        }, completion: { finished in
            self.nextTask()
        })

The issue is that the completion block doesn't run 0.8 seconds later - it doesn't run until AFTER the previous code completes.

Is there anything I can do other than run that code in a background thread with DispatchQueue.global(qos: .background).async? Or is there a way to improve the background thread's speed to completion?

My animation isn't interrupted if done in the background thread, but it takes much much longer to complete in the background - like 20-30 seconds instead of 4-5.

Thanks for any help you can share here!


Solution

  • I am calling a function which creates several arrays of UIImage objects and takes 4-5 seconds to complete

    You absolutely cannot run that on the main thread. A refresh frame is 1/120 second on a modern device. You must not cause frames to be lost. You are blocking user interaction while that happens. The screen will be frozen and the Watchdog process will kill your app dead on the spot. (Unfortunately the Simulator doesn't show you that, nor does running from Xcode on the device; but if you run the app on the device, stop it, and run it from the device rather than from Xcode, you'll see what I mean.)

    The issue is that the completion block doesn't run 0.8 seconds later - it doesn't run until AFTER the previous code completes.

    As you've surmised, there's a re-entrancy problem here. You need to get off the main thread to allow the completion block to happen. I'm actually surprised that the animation even starts (does it?). Nothing at all can happen while you are still running code on the main thread.

    You could call async_after with a short delay to hop off the main thread momentarily and then re-enter it, long enough to let a refresh frame happen, so the system can do some work. But that would just be a way of letting the animation start. Once you're back on the main thread, you'll be blocking it again.

    As for speed: a QOS of .background is the slowest choice, so you're getting what you asked for. In general, though, don't use the global queues; make your own queue. I can't imagine what you're doing that can take so long, and maybe that is what you should really be asking about, but you absolutely cannot do it on the main thread.