Is CADisplayLink callback always runs on the main thread? - ios

I couldn't find any direct answer online nor within the docs about this.
If I'm setting up CADisplayLink with the following:
let displayLink = CADisplayLink(target: self, selector: #selector(updateShimmer))
displayLink.add(to: .current, forMode: .common)
#objc func updateShimmer() {
print(Thread.isMainThread)
}
I'm getting true. I know that I can wrap this within a DispatchQueue.main but I was wondering, is it always dispatched on the main queue? Or should I wrap it anyway?

You do not need to manually dispatch code inside your display link handler to the main thread. Timers and display links added to a particular run loop will always run on the thread associated with that run loop. For more information, see Threading Programming Guide: Run Loops.
Bottom line, if you add(to:forMode:) to the main run loop, the main run loop always runs on the main thread.
That having been said, if you want to make sure it always runs on the main thread, I would suggest being explicit and adding it to .main, not just .current. That removes any ambiguity:
let displayLink = CADisplayLink(target: self, selector: #selector(updateShimmer(_:)))
displayLink.add(to: .main, forMode: .common)
Note, I also tweaked the signature of updateShimmer to accept a parameter. The display link will be passed to it. It’s often useful to have that reference inside the method. And, regardless, it makes one's code more self-evident: You can just glance at this method now and understand that this is a display link handler:
#objc func updateShimmer(_ displayLink: CADisplayLink) {
...
}

Related

Timer.ScheduledTimer not initialising quickly enough if started on main thread

In the accepted answer for Timer.scheduledTimer not firing, it is emphasised to start the timer on the main thread to ensure that it fires. However, if I do that then I often end up with the timer being slow to initialise, and therefore failing in its purpose as a debouncer. Just wondering if there is something I am doing wrong, or a better way of doing this.
My problem (pseudocode at the bottom):
I use a JWT to authenticate my server calls, and I check this locally to see if it's expired before submitting it. However, I don't want several network calls to notice the expired JWT all at once and submit several refresh requests, so I use a semaphore to ensure only one call at a time is checking/renewing the JWT. I also use a dispatchGroup to delay the original network call until after the checking/renewing is done. However, if the refresh fails I want to avoid all the queued calls then trying it again. I don't want to block all refresh calls forever more with a boolean, so I thought I would create a scheduledTimer to block it. However, if I create it on the main thread, there's a delay before it's created and the released network calls submit a few more refresh attempts before they're blocked.
Questions
Should I just create the timer on the local thread to ensure there's no delay (I presume the main thread is occupied with some UI tasks which is why the timer doesn't get created instantly?)
More generally, is there a better way of doing this? I suspect there is - I tried playing with adding items to a queue, and then cancelling them, but then I began getting worried about creating work items with out of date values of functions, and capturing things in closures etc (it was a while ago, I can't remember the details), so I went with my current bodge.
This might all be easier if I was using await/async, but our app supports all the way back to iOS12, so I'm stuck with nests of completion handlers.
Hopefully this pseudocode is accurate enough to be helpful!
private static let requestQueue: DispatchQueue = DispatchQueue(label: "requestQueue", qos: .userInteractive, attributes: .concurrent)
public static let jwtValidityCheckSemaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
private static var uglyHackTimer: Timer?
#objc private class func clearUglyHackTimer(){
uglyHackTimer?.invalidate()
uglyHackTimer = nil
}
class func myNetworkCall(for: myPurposes){
let group = DispatchGroup()
jwtValidityCheckSemaphore.wait()
if (uglyHackTimer?.isValid ?? false){
jwtValidityCheckSemaphore.signal()
return
}
group.enter()
if jwtIsInvalid(){
refreshJWT(){success in
if !success{
DispatchQueue.main.async{
self.uglyHackTimer = Timer.scheduledTimer(timeInterval: TimeInterval(2), target: self, selector: #selector(clearUglyHackTimer), userInfo: nil, repeats: false)
}
}
group.leave()
jwtValidityCheckSemaphore.signal()
}
}else{
group.leave()
jwtValidityCheckSemaphore.signal()
}
// Make the original network call
newNetworkRequest = DispatchWorkItem{
// Blah, blah
}
group.notify(queue: requestQueue, work: newNetworkRequest)
}

Timer and debug issues of running iOS app on M1 MacOS

My iOS app runs a repeatable timer inside a main dispatch queue to update the UI screen status periodically. It runs normally on iPhone and iPad. When it runs on a M1 MacOS, the timer seems not functioning as expected - the timer is called much much faster than the defined time interval.
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true,
block: {
// runs code to update UI
})
}
Besides, is there any good way to debug the iOS app running on M1 MacOS (like setting debug breakpoints in XCode) instead of re-installing/upgrading the iOS app from App Store after change each time?
Thank you very much for your advices.
We do not have the broader context in which you are calling the code in your question. Theoretically, there could be some race condition that performs differently on the different machines. Or perhaps when testing on the M1 device, there was a subtle difference in the testing procedure.
Regardless, there are a few defensive programming techniques that can solve this sort of problem:
I would make sure that timer is weak variable. The RunLoop will keep a strong reference to the timer for you, so you do not need to keep your own strong reference to it:
weak var timer: Timer?
By doing this, when the timer is invalidated, this reference will be set to nil automatically.
When creating a timer, invalidate the prior timer, if any. This will protect you should you ever accidentally call this routine more than once:
DispatchQueue.main.async {
self.timer?.invalidate() // invalidate prior timer, if any
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self else {
timer?.invalidate()
return
}
// runs code to update UI
}
}
Note that in addition to the invalidate-before-scheduling pattern, I also
I use [weak self] pattern in closure, ensuring that the repeating timer cannot not unintentionally keep a strong reference to the current object.
I use guard let self = self else ... pattern to invalidate the timer and return should the object in question be deallocated.
But the critical note is that we want to use [weak self] with repeating timers to guard against strong reference cycles.
Optionally, while the above will stop the timer if the object has been deallocated when it next fires, I would generally also explicitly stop the timer as soon as the object is deallocated.
deinit {
timer?.invalidate()
}
The combination of the above steps will ensure that you can only have one timer per object, regardless of how often you call this routine. These steps will help prevent ever having multiple timers running at the same time.

When updating my UI in response to an async action, where should I call DispatchQueue?

In my iOS app, I make a lot of web requests. When these requests succeed / fail, a delegate method in the view controller is triggered. The delegate method contains code that is responsible for updating the UI. In the following examples didUpdate(foo:) is the delegate method, and presentAlert(text:) is my UI update.
Without DispatchQueue, the code would like this:
func didUpdate(foo: Foo) {
self.presentAlert(text: foo.text)
}
func presentAlert(text: String) {
let alertController = ...
self.present(alertController, animated: true)
}
When it comes to using DispatchQueue to make sure my UI will update quickly, I start to lose my ability to tell what's actually happening in the code. Is there any difference between the following two implementations?
First Way:
func didUpdate(foo: Foo) {
self.presentAlert(text: foo.text)
}
func presentAlert(text: String) {
let alertController = ...
DispatchQueue.main.async {
self.present(alertController, animated: true)
}
}
Second way:
func didUpdate(foo: Foo) {
DispatchQueue.main.async {
self.presentAlert(text: foo.text)
}
}
func presentAlert(text: String) {
let alertController = ...
self.present(alertController, animated: true)
}
Does it matter which approach I go with? It seems like having the DispatchQueue block inside of the presentAlert function is better, so I don't have to include DispatchQueue.main.async any time I want to call presentAlert?
Is it only necessary to explicitly send a block to the main queue when you (or a framework you are using) has "moved" yourself into a background queue?
If there are any external resources that may help my understanding of GCD, please let me know!
Does it matter which approach I go with? It seems like having the DispatchQueue block inside of the presentAlert function is better, so I don't have to include DispatchQueue.main.async any time I want to call presentAlert?
There is no difference between the two approaches. But the disadvantage with the second approach, like you said, is you have to wrap all the calls to presentAlert around the DispatchQueue.main.async closure.
Is it only necessary to explicitly send a block to the main queue when you (or a framework you are using) has "moved" yourself into a background queue?
If your question here is whether there is going to be a problem if you dispatch to the main queue from the main queue, then the answer is no. If you dispatch asynchronously on the main queue from within the main queue, all it does is call your method later in the run loop.
If there are any external resources that may help my understanding of GCD, please let me know!
There are many sources on the Internet to understand GCD better. Check out this Raywenderlich tutorial. Its a good place to start.
My recommendation would be, if you have a central class that handles all the web service calls, it might be better to invoke the completion callback closure on the main queue once you parse your data after getting the web service response. This way, you won't have to keep dispatching to the main queue in your view or viewcontroller classes.

Why is my swift code pausing at the loading screen?

if timerRunning == false{
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("counting"), userInfo: nil, repeats: true)
timerRunning = true
}
while timerLabel.text != "0"{
gameViewStillRunning = false
}
if gameViewStillRunning == false{
self.performSegueWithIdentifier("segue", sender: nil)
}
The purpose of this code is to display a label counting down, and then when it hits 0, the scene should switch to a different ViewController. This code doesn't get any errors but when I run it, the program does not get any further than the loading screen. Any suggestions?
It looks like you are running a while loop on the main thread which is also the thread responsible for drawing the UI. As long as that thread is stuck in your while loop there's no way for it's run loop to continue and no opporunity for it to update the display or respond to user interaction.
In addition if you look at the NSTimer documentation you might notice that scheduledTimerWithTimeInterval states that it Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode. That timer is scheduling a call to your counting function in the main thread's run loop but since that thread is stuck spinning forever in your while it will never have a chance to execute so it has no chance to update that timerLabel.text.
Rather than attempting to block execution with your while loop while polling for some condition, a better solution would be to allow the run loop to continue and to react when the timer calls your counting function. Let your application react to events (like the time remaining changing) regardless of when or how they happen rather than trying to control the exact sequence of execution.
The problem is that this is an infinite loop:
while timerLabel.text != "0"{
gameViewStillRunning = false
}
The body of the loop doesn't change the value of timerLabel.text, so the condition (the test in the first line) keeps failing, and the loop just loops forever — and the code, and so the entire app, just hangs at that point, coming to a dead stop permanently.

UIScrollView pauses NSTimer until scrolling finishes

While a UIScrollView (or a derived class thereof) is scrolling, it seems like all the NSTimers that are running get paused until the scroll is finished.
Is there a way to get around this? Threads? A priority setting? Anything?
An easy & simple to implement solution is to do:
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
For anyone using Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
tl;dr the runloop is handing scroll related events. It can't handle any more events — unless you manually change the timer's config so the timer can be processed while runloop is handling touch events. OR try an alternate solution and use GCD
A must read for any iOS developer. Lots of things are ultimately executed through RunLoop.
Derived from Apple's docs.
What is a Run Loop?
A run loop is very much like its name sounds. It is a loop your thread
enters and uses to run event handlers in response to incoming events
How delivery of events are disrupted?
Because timers and other periodic events are delivered when you run
the run loop, circumventing that loop disrupts the delivery of those
events. The typical example of this behavior occurs whenever you
implement a mouse-tracking routine by entering a loop and repeatedly
requesting events from the application. Because your code is grabbing
events directly, rather than letting the application dispatch those
events normally, active timers would be unable to fire until after
your mouse-tracking routine exited and returned control to the
application.
What happens if timer is fired when run loop is in the middle of execution?
This happens A LOT OF TIMES, without us ever noticing. I mean we set the timer to fire at 10:10:10:00, but the runloop is executing an event which takes till 10:10:10:05, hence the timer is fired 10:10:10:06
Similarly, if a timer fires when the run loop is in the middle of
executing a handler routine, the timer waits until the next time
through the run loop to invoke its handler routine. If the run loop is
not running at all, the timer never fires.
Would scrolling or anything that keeps the runloop busy shift all the times my timer is going to fire?
You can configure timers to generate events only once or repeatedly. A
repeating timer reschedules itself automatically based on the
scheduled firing time, not the actual firing time. For example, if a
timer is scheduled to fire at a particular time and every 5 seconds
after that, the scheduled firing time will always fall on the original
5 second time intervals, even if the actual firing time gets delayed.
If the firing time is delayed so much that it misses one or more of
the scheduled firing times, the timer is fired only once for the
missed time period. After firing for the missed period, the timer is
rescheduled for the next scheduled firing time.
How can I change the RunLoops's mode?
You can't. The OS just changes itself for you. e.g. when user taps, then the mode switches to eventTracking. When the user taps are finished, the mode goes back to default. If you want something to be run in a specific mode, then it's up to you make sure that happens.
Solution:
When user is scrolling the the Run Loop Mode becomes tracking. The RunLoop is designed to shifts gears. Once the mode is set to eventTracking, then it gives priority (remember we have limited CPU cores) to touch events. This is an architectural design by the OS designers.
By default timers are NOT scheduled on the tracking mode. They are scheduled on:
Creates a timer and schedules it on the current run loop in the
default mode.
The scheduledTimer underneath does this:
RunLoop.main.add(timer, forMode: .default)
If you want your timer to work when scrolling then you must do either:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode
RunLoop.main.add(timer, forMode: .tracking) // AND Do this
Or just do:
RunLoop.main.add(timer, forMode: .common)
Ultimately doing one of the above means your thread is not blocked by touch events.
which is equivalent to:
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.
Alternative solution:
You may consider using GCD for your timer which will help you to "shield" your code from run loop management issues.
For non-repeating just use:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// your code here
}
For repeating timers use:
See how to use DispatchSourceTimer
Digging deeper from a discussion I had with Daniel Jalkut:
Question: how does GCD (background threads) e.g. a asyncAfter on a background thread get executed outside of the RunLoop? My understanding from this is that everything is to be executed within a RunLoop
Not necessarily - every thread has at most one run loop, but can have zero if there's no reason to coordinate execution "ownership" of the thread.
Threads are an OS level affordance that gives your process the ability to split up its functionality across multiple parallel execution contexts. Run loops are a framework-level affordance that allows you to further split up a single thread so it can be shared efficiently by multiple code paths.
Typically if you dispatch something that gets run on a thread, it probably won't have a runloop unless something calls [NSRunLoop currentRunLoop] which would implicitly create one.
In a nutshell, modes are basically a filter mechanism for inputs and timers
Yes, Paul is right, this is a run loop issue. Specifically, you need to make use of the NSRunLoop method:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
You have to run another thread and another run loop if you want timers to fire while scrolling; since timers are processed as part of the event loop, if you're busy processing scrolling your view, you never get around to the timers. Though the perf/battery penalty of running timers on other threads might not be worth handling this case.
This is the swift version.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
for anyone use Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
Tested in swift 5
var myTimer: Timer?
self.myTimer= Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
//your code
}
RunLoop.main.add(self.myTimer!, forMode: .common)

Resources