What is the proper way to make CADisplayLink callback functions run on a background thread? I'm creating the Display Link with:
let displayLink = CADisplayLink(target: self, selector: #selector(self.renderBackground))
if let displayLink = displayLink {
displayLink.preferredFramesPerSecond = 30
DispatchQueue.main.async {
displayLink.add(to: .current, forMode: .common)
}
}
This works, but when I add a breakpoint to the renderBackground function, it shows it is on the Main thread:
It appears to be on the main thread?
A CADisplayLink is intended to provide callbacks synchronized with the screen refresh, which takes place on the main thread. It doesn't make sense to synchronize an event that occurs on the main thread with calls on another thread.
I suspect there is no way to do this.
Related
I've got a precise timer that needs to be update every 40ms (most precisely than possible). On iOS10 it's good (I use the new scheduleRepeating method) but on iOS9 I need to use the old way (scheduledTimer) and it's pretty laggy (sometimes 24ms, sometimes its 72...), so my hardware interface and visual effects and lagging.
Any suggestion?
func launchTimer() {
if #available(iOS 10.0, *) {
startTickTimer()
} else {
let timerQueue = DispatchQueue(label: "my.queue.tickTimer", attributes: .concurrent)
self.swiftTimer = Timer.scheduledTimer(timeInterval: period, target: self, selector: #selector(executeTimer), userInfo: nil, repeats: true)
timerQueue.async {
RunLoop.current.add(self.swiftTimer!, forMode: RunLoopMode.defaultRunLoopMode)
}
}
}
static func startTickTimer() {
let queue = DispatchQueue(label: "my.queue.tickTimer", attributes: .concurrent)
DMXTimer.tickTimer?.cancel()
DMXTimer.tickTimer = DispatchSource.makeTimerSource(queue: queue)
DMXTimer.tickTimer?.scheduleRepeating(deadline: .now(), interval: .milliseconds(40), leeway: .seconds(1))
DMXTimer.tickTimer?.setEventHandler {
executeTimer()
}
DMXTimer.tickTimer?.resume()
}
static func executeTimer() {
print("hello moto")
}
From Apple doc about NSTimer:
A timer is not a real-time mechanism. If a timer’s firing time occurs
during a long run loop callout or while the run loop is in a mode that
isn't monitoring the timer, the timer doesn't fire until the next time
the run loop checks the timer. Therefore, the actual time at which a
timer fires can be significantly later. See also Timer Tolerance.
If you want a very precise timer you can check an implementation from Apple Tech Note and adapt in Swift or use a CADisplayLink for display updates.
I would suggest to use NSThread and loop inside instead of timer. It is more complicated but, imho, it has more settings and flexible behaviour
II have an NSTimer set to fire each second. Using the Simulator this will call the eachSecond() method correctly after I navigate away from the app by pressing the home button. However, on a real iPhone 6, once I navigate away eachSeconds is not called again until the app is open again.
Why might this behave differently on a real device? Is there anyway to ensure it will fire each second in this use case?
Its a fitness app, so needs to record duration when the phone locks or user navigates away momentarily.
Thanks,
lazy var timer = NSTimer()
fun init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(RunViewController.eachSecond(_:)), userInfo: nil, repeats: true)
}
func eachSecond() {
// update UI etc.
}
An NSTimer will not fire when your app is suspended. Background execution is different when running under XCode; background execution limits do not apply.
In order to determine how much time has elapsed while your app is suspended, you can store a timestamp in applicationDidEnterBackground and calculate the elapsed time based on the difference between the saved timestamp and current time in applicationWillEnterForeground
You need to add the timer to the main run loop.
func init() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(RunViewController.eachSecond(_:)), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
I want to loop dispatch_after for looping showing images like this:
while isRunning {
let delay = Int64(Double(NSEC_PER_SEC) * settings.delay)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), {
dispatch_async(dispatch_get_main_queue(), {
self.showNextImage()
})
})
}
It supposed to call showNextImage every delay seconds. But it stucks in infinite loop without showing images. I dont know how to solve this problem. Any help is appreciated.
Loop is dispatching infinite dispatch_after because isRunning is yes and loop is not waiting , better to put some count (i<10) or use NSTimer to certain condition then invalidate.
if (self.stimerShowNextImage.isValid) {
[self.stimerShowNextImage invalidate];
self.stimerShowNextImage = nil;
}
self.stimerShowNextImage = [NSTimer scheduledTimerWithTimeInterval: 5
target: self
selector: #selector(showNextImage)
userInfo: nil
repeats: YES];
Above is the timerCode with 5 second delay , in ViewWillDisapper you need to invalidate it , Also before scheduling timer you need to check its not already running.
Look in your code dispatch_after already running in main thread. And again inside main thread using. i mean not need.
when it is need means if u r using other than main thread outside and after u r using uielements, that time use main thread inside.
I have scheduled and NSTimer on main thread.
func startTimer(){
MYClass.runUIBlock { () -> Void in
self.invalidateRecordTimer()
self.updateTimerForInactiveRecord(self.fiveMinutesInSeconds)
}
}
private func updateTimerForInactiveRecord(timeToRecord:Double){
recordTimer = NSTimer(timeInterval: timeToRecord, target: self, selector:"collectData", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(recordTimer!, forMode: NSRunLoopCommonModes)
}
The timer keeps on running even if I am in background. Wanted to ask, is it safe to keep the timer ON without invalidating it before the background timer gets over ? Or do I need to check for available background time and invalidate the timer before background timer gets over ?
At present, the timer keeps on running indefinitely in the background.
An NSTimer can run only a max of 10 minutes when in background. Apple has done this purposefully to avoid battery drainage. If you are not supposed to do important activity when in background then as a good citizen it is advised to stop the timer when going in background.
I have this, but seems overly complicated:
var connectionTimer = MZTimerLabel(timerType: MZTimerLabelTypeTimer)
connectionTimer.delegate = self
connectionTimer.tag = 0
connectionTimer.setCountDownTime(5)
connectionTimer.start()
func timerLabel(timerLabel: MZTimerLabel!, finshedCountDownTimerWithTime countTime: NSTimeInterval) {
self.callFinished()
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue()) {
self.callFinished()
}
This is part of the awesome and versatile library Grand Central Dispatch, which will allow you to perform not just delays but all sorts of concurrent operations you will almost certainly have threading bugs in! Luckily, since in your case you are calling back to the main queue, none of your code will execute concurrently, so there's nothing to worry about in this case.
Try this
let delayTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("delayed"), userInfo: nil, repeats: false)
func delay() { println ("hi") }
It will call delay five seconds later.