I am trying to run a display link in a thread other than main but it simply doesn't work. I have a simple dispatch queue created like queue = DispatchQueue(label: "xyz") and then I create the display link as usual:
queue.async {
self.displayLink = CADisplayLink(target: self, selector: #selector(render))
self.displayLink.add(to: .current, forMode: .common)
}
The selector never gets called. Upon checking the currentMode of the RunLoop I see it is nil. What am I missing?
Thanks
Due to the reason that your queue is non-main, the current run loop won't trigger by itself.
You should call current.run() manually after displayLink been added.
queue.async {
self.displayLink = CADisplayLink(target: self, selector: #selector(render))
let current = RunLoop.current
self.displayLink.add(to: current, forMode: .common)
current.run()
}
Related
I am facing one strange issue.
So I have created instance and allocated like below
var displayLink:CADisplayLink?
private func setupDisplayLink () {
self.displayLink = CADisplayLink(target: self, selector: #selector(displayLinkDidFire(link:)))
self.displayLink?.preferredFramesPerSecond = 30
self.displayLink?.add(to: .main, forMode: .common)
}
Now to remove display link I have from this Answer
self.displayLink?.remove(from: .main, forMode: .common) // comment this line and view controller correctly deallocated
self.displayLink?.invalidate()
because of line self.displayLink?.remove(from: .main, forMode: .common) my deinit method not called of view controller
In memory graph I am not able to identify the real problem. I was lucky that I have tried to comment that line and it works
Why that so ?
Stop displayLink like this:
displayLink?.invalidate()
displayLink = nil
This is what I try to do in code:
for i in 1...1000000000 {
print(i)
self.title = "\(i)"
}
on console it prints everything, but I can't see any updates in my navigation bar. Why?
Actually you could have a look at CADisplayLink which basically is a timer that is synchronized with the refresh of the display. At its most basic form it would be something like this:
func createDisplayLink() {
let displaylink = CADisplayLink(target: self, selector: #selector(step))
displaylink.add(to: .current, forMode: .defaultRunLoopMode)
}
#objc func step(displaylink: CADisplayLink) {
// Do the updates
}
Note: Please note that step will be called a lot, essentially on each screen update, which is 60-120 fps on current devices.
Quick Context: I am building a game where players listen to a song and have to press a button every 4th beat. I am trying to use a timer that maintains the beat of the song. I used NSTimer, however the time tends to get off beat after a while. I also tried GCD, however the timer also got off beat. Is there a way to make a timer that always maintains its paste, like a metronome?
GCD:
//GCD example that did not work
var timerf: DispatchSourceTimer!
func handleStartTimer() {
let queue = DispatchQueue(label: "com.domain.app.timer", qos: .userInteractive)
timerf = DispatchSource.makeTimerSource(flags: .strict, queue: queue)
timerf.scheduleRepeating(deadline: .now(), interval: 0.585, leeway: .nanoseconds(0))
timerf.setEventHandler {
self.handleAddSecondtoTime()
}
timerf.resume()
//NSTimer example that did not work
timer = Timer.scheduledTimer(timeInterval: 0.585, target: self, selector: #selector(self.handleAddSecondtoTime), userInfo: nil, repeats: true)
}
The following code snippet works perfectly when called outside a completion block, but the timer is never fired when I set it up inside the block. I don't understand why there is a difference:
self.timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.foo),
userInfo: nil,
repeats: true)
I was not using the self references when calling it initially outside the block, but then once inside, it was required. However I tested the exact same code outside the block again and it does still work.
The block is a completion hander that is called after asking permission for HealthKit related information.
The issue is that the completion block in question was probably not running on the main thread and therefore didn't have a run loop. But timers need to be scheduled on a run loop, and while the main thread has one, most background threads do not (unless you add one, yourself).
To fix this, in that completion handler, dispatch the creation of the timer back to the main thread and it should work fine:
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)
}
Or use a dispatch source timer (a timer that can be scheduled for a background queue, and doesn't require a run loop).
var timer: DispatchSourceTimer!
private func startTimer() {
let queue = DispatchQueue(label: "com.domain.app.timer")
timer = DispatchSource.makeTimerSource(queue: queue)
timer.setEventHandler { [weak self] in
// do something
}
timer.schedule(deadline: .now(), repeating: 1.0)
timer.resume()
}
For syntax for earlier version of Swift, see previous revision of this answer.
Another reason why Timer() might not work work is how it's created. I had the same problem, and everything I tried didn't solve it, including instantiating on the main thread. I stared at this for quite a while until I realized (stupidly) that I was creating it differently. Instead of Timer.scheduledTimer
I instantiated it using
let timer = Timer(timeInterval: 4.0, target: self, selector: #selector(self.timerCompletion), userInfo: nil, repeats: true)
In my case I had to actually add it to a run loop to get it to run. Like this
RunLoop.main.add(timer, forMode: RunLoop.Mode.default)
This may sound obvious, but I had a similar problem, the timer just wouldn't fire and the reason is that it wasn't in the main thread...No errors, just never fired.
Put in the main thread and at least you have a shot at it!
DispatchQueue.main.async {
//insert your timer here
}
I am trying to use the NSTimer to increment the progress bar in my app when recording voice (see the screenshot)
let timedClock = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
internal func Counting(timer: NSTimer!) {
if timeCount == 0 {
//self.timedClock = nil
stopRecording(self) //performs segue to another view controller
} else {
timeCount--;
self.timer.text = "\(timeCount)"
}
print("counting called!")
progressBar.progress += 0.2
}
The progress bar works only for the first time after I compile and run the project. When the recording is finished, the app performs segue to another view controller to play the recorded audio. However, when I go back to the view for recording, the timer/progress bar automatically runs. I suspect the NSTimer object is still alive on the NSRunLoop. So I was wondering how to prevent the NSTimer from automatically running.
Inspired by the answer in this SO thread, I tried the following, but the NSTimer still automatically runs.
let timedClock = NSTimer(timeInterval: 1, target: self, selector: "Counting:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timedClock, forMode: NSRunLoopCommonModes)
This happens because when your controller created it's properties are automatically initialized. According to Apple Docs (and method's name) scheduledTimerWithTimeInterval create and return scheduled timer. So if you only want create your timer and call it by trigger function use it like this:
class MyClass {
var timer: NSTimer?
...
func enableTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
}
func disableTimer() {
timer?.invalidate()
timer = nil
}
...
}
Sorry for the quick self-answer, as I just found out that I can use the invalidate() method to prevent the timer from automatically firing:
timedClock.invalidate()
Hope it helps someone in the future!