swiftmultithreadingmacosnstimernsimageview

NSImageView update delayed when set from an NSTimer


I'm working on a project in Swift that requires strict control of image display and removal timings in certain sections. I'm finding that when I set the image property of an NSImageView from inside a block that's fired by a Timer, the actual display of the image on the screen is delayed by up to a second after the assignment is complete. (This is measured by eyeballing it and using a stopwatch to gauge the time between when an NSLog line is written and when the image actually appears on-screen.)

Triggering image display with a click appears to happen instantaneously, whether it's done by setting the image property of an existing NSImageView, or constructing one on the spot and adding it as a subview.

I have attempted to reduce the behavior down to a fairly simple test case, which, after basic setup of the view (loading the images into variables and laying out several target image locations, which are NSImageViews stored to the targets array), sets a Timer, with an index into the targets array stored in its userInfo property, to trigger the following method:

@objc func testATimer(fromTimer: Timer) {
    if let targetLocation = fromTimer.userInfo as? Int {
        NSLog("Placing target \(targetLocation)")
        targets[targetLocation].image = targetImage

        OperationQueue.main.addOperation {
            let nextLocation = targetLocation + 1
            if (nextLocation < self.targets.count) {
                NSLog("Setting timer for target \(nextLocation)")
                let _ = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.testATimer(fromTimer:)), userInfo: nextLocation, repeats: false)
            }
        }
    }
}

The amount of time observed between the appearance of the log entry "Placing target x" and that of the image that is set to be displayed in the very next line seems to average between 0.3 and 0.6 seconds. This is far beyond the delay that this project can tolerate, as the image will only be on screen for a total of 1 second at a time.

My best guess as to why this is happening is that it is something to do with Timers running on a separate thread from the main display thread; however, I do not have enough experience with multi-threaded programming to know exactly how to mitigate that.

Any assistance would be most appreciated, and if I've left out information that would make answering easier (or possible) I'm more than happy to give it.


Solution

  • Well, after poking at it with some helpful people on in #macdev on irc.freenode.net, I found that the source of the problem was the program scaling the image down on the fly every time it set it to the NSImageView. Reducing the size of the image ahead of time solved the problem. (As did setting the image once, then hiding and showing the NSImageView instead.)