Why is my swift code pausing at the loading screen? - ios

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.

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.

Where should I use nsrunloop? [duplicate]

Can anyone explain for what is NSRunLoop? so as I know NSRunLoop is a something connected with NSThread right? So assume I create a Thread like
NSThread* th=[[NSThread alloc] initWithTarget:self selector:#selector(someMethod) object:nil];
[th start];
-(void) someMethod
{
NSLog(#"operation");
}
so after this Thread finishes his working right? why use RunLoops or where to use ? from Apple docs I have read something but its not clear for me, so please explain as simple as it possible
A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, mouse, timers, etc).
Each NSThread has its own run loop, which can be accessed via the currentRunLoop method.
In general, you do not need to access the run loop directly, though there are some (networking) components that may allow you to specify which run loop they will use for I/O processing.
A run loop for a given thread will wait until one or more of its input sources has some data or event, then fire the appropriate input handler(s) to process each input source that is "ready.".
After doing so, it will then return to its loop, processing input from various sources, and "sleeping" if there is no work to do.
That's a pretty high level description (trying to avoid too many details).
EDIT
An attempt to address the comment. I broke it into pieces.
it means that i can only access/run to run loop inside the thread
right?
Indeed. NSRunLoop is not thread safe, and should only be accessed from the context of the thread that is running the loop.
is there any simple example how to add event to run loop?
If you want to monitor a port, you would just add that port to the run loop, and then the run loop would watch that port for activity.
- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode
You can also add a timer explicitly with
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
what means it will then return to its loop?
The run loop will process all ready events each iteration (according to its mode). You will need to look at the documentation to discover about run modes, as that's a bit beyond the scope of a general answer.
is run loop inactive when i start the thread?
In most applications, the main run loop will run automatically. However, you are responsible for starting the run loop and responding to incoming events for threads you spin.
is it possible to add some events to Thread run loop outside the thread?
I am not sure what you mean here. You don't add events to the run loop. You add input sources and timer sources (from the thread that owns the run loop). The run loop then watches them for activity. You can, of course, provide data input from other threads and processes, but input will be processed by the run loop that is monitoring those sources on the thread that is running the run loop.
does it mean that sometimes i can use run loop to block thread for a time
Indeed. In fact, a run loop will "stay" in an event handler until that event handler has returned. You can see this in any app simply enough. Install a handler for any IO action (e.g., button press) that sleeps. You will block the main run loop (and the whole UI) until that method completes.
The same applies to any run loop.
I suggest you read the following documentation on run loops:
https://developer.apple.com/documentation/foundation/nsrunloop
and how they are used within threads:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
Run loops are what separates interactive apps from
command-line tools.
Command-line tools are launched with parameters, execute their command, then exit.
Interactive apps wait for user input, react, then resume waiting.
From here
They allow you to wait till user taps and respond accordingly, wait till you get a completionHandler and apply its results, wait till you get a timer and perform a function. If you don't have a runloop then you can't be listening/waiting for user taps, you can't wait till a network call is happening, you can't be awoken in x minutes unless you use DispatchSourceTimer or DispatchWorkItem
Also from this comment:
Background threads don't have their own runloops, but you can just add
one. E.g. AFNetworking 2.x did it. It was tried and true technique for
NSURLConnection or NSTimer on background threads, but we don't do this
ourselves much anymore, as newer APIs eliminate the need to do so. But
it appears that URLSession does, e.g., here is simple request, running [see the left panel of the image]
completion handlers on the main queue, and you can see it has a run
loop on background thread
Specifically about: "Background threads don't have their own runloops". The following timer fails to fire for an async dispatch:
class T {
var timer: Timer?
func fireWithoutAnyQueue() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
print("without any queue") // success. It's being ran on main thread, since playgrounds begin running from main thread
})
}
func fireFromQueueAsnyc() {
let queue = DispatchQueue(label: "whatever")
queue.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
print("from a queue — async") // failed to print
})
}
}
func fireFromQueueSnyc() {
let queue = DispatchQueue(label: "whatever")
queue.sync {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
print("from a queue — sync") // success. Weird. Read my possible explanation below
})
}
}
func fireFromMain() {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
print("from main queue — sync") //success
})
}
}
}
I think the reason the sync block also runs is because:
sync blocks usually just get executed from within their source queue. In this example, source queue is main queue, the whatever queue is the destination queue.
To test that I logged RunLoop.current inside every dispatch.
The sync dispatch had the same runloop as main queue. While the RunLoop within the async block was a different instance from the others. You might be thinking how why does RunLoop.current return a different value. Isn't it a shared value!? Great question! Read further:
IMPORTANT NOTE:
The class property current is NOT a global variable.
Returns the run loop for the current thread.
It's contextual. It's visible only within the scope of the thread ie Thread-local storage. For more on that see here.
This is a known issue with timers. You don't have the same issue if you use DispatchSourceTimer
RunLoops are a bit of like a box where stuff just happens.
Basically, in a RunLoop, you go to process some events and then return. Or return if it doesn't process any events before the timeout is hit.
You can say it as similar to asynchronous NSURLConnections, Processing data in the background without interfering your current loop and but at the same time, you require data synchronously.
Which can be done with the help of RunLoop which makes your asynchronous NSURLConnection and provides data at calling time.
You can use a RunLoop like this:
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) {
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
}
In this RunLoop, it will run until you complete some of your other work and set YourBoolFlag to false.
Similarly, you can use them in threads.
Hope this helps you.
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
From here
The most important feature of CFRunLoop is the CFRunLoopModes. CFRunLoop works with a system of “Run Loop Sources”. Sources are registered on a run loop for one or several modes, and the run loop itself is made to run in a given mode. When an event arrives on a source, it is only handled by the run loop if the source mode matches the run loop current mode.
From here
iOS RunLoop
RunLoop(EventLoop, Looper) is an implementation of EventLoop (event processing loop) pattern. It is based on NSRunLoop (which a wrapper of CFRunLoopRef)
Official doc
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
Single thread can have single RunLoop in a single mode. Only events with this mode will be processed all others will be waiting when RunLoop will be started at that mode
RunLoop is a mechanism (based on loop(for, while)) which move a scheduled task(e.g Callback Queue) to a thread(thread stack). RunLoop works(event processing loop) when Thread Stack is empty.
event processing loop is when RunLoop between .entry and .exit. During it RunLoop handles all scheduled task in specific mode. All others modes with their own Queues will be managed after
Application by default has a main thread with RunLoop(main loop). In other cases you should create it manually
main run loop is responsible for draining the main queue in an app.
//Run loop for the current thread
RunLoop.current
//Run loop of the main thread.
RunLoop.main
Mode
A run loop mode is a collection of input sources and timers to be monitored and a collection of run loop observers to be notified.
modes:
default - used by default
tracking - for example when you scroll UITableView scrollViewDidScroll
common(is a pseudo mode like [default, tracking])
<custom> - you are able to create your own mode
//current input mode
RunLoop.current.currentMode
For example:
UIView.draw(_ rect:), button action... uses default mode
NSObject.perform(_:with:afterDelay:) uses default mode
DispatchQueue.main.async uses common mode
Timer.scheduledTimer uses default mode. That is why when UI scrolling occurring(in tracking mode) your timer is not fired(in default mode). To fix it use common mode - RunLoop.main.add(timer, forMode: .common)
Combine RunLoop.main vs DispatchQueue.main(.receive(on:, options:)). RunLoop.main uses RunLoop.perform(_:) which uses default mode, DispatchQueue.main uses DispatchQueue.main.async which uses common mode
input sources and timers
Run loop receives events:
Input sources - asynchronous events(as fired) messages
Port-based - from another thread or process. signaled automatically by the kernel
Custom Input Sources - user-initiated events - user actions, network events. must be signaled manually from another thread
performSelector: onThread
Timer sources - synchronous events(at specific time) timers
They can be added to several modes
observers
monitor RunLoop's state changes
Create RunLoop
create new thread, setup RunLoop and start the thread
create RunLoop
RunLoop.current
A run loop must have at least one input source or timer to monitor
RunLoop.add(_ timer: Timer, forMode mode: RunLoop.Mode)
RunLoop.add(_ aPort: Port, forMode mode: RunLoop.Mode)
run RunLoop
RunLoop.run()
let thread = Thread {
//1. create RunLoop
//create a new one or return existing run loop for current thread
//use RunLoop.current instead of RunLoop()
let customRunLoop = RunLoop.current
//add observer for current RunLoop for cpecufic mode
CFRunLoopAddObserver(CFRunLoopGetCurrent(), customObserver, CFRunLoopMode.commonModes)
//2. A run loop must have at least one input source or timer to monitor
let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
//.default mode
}
customRunLoop.add(timer, forMode: .default)
//3. run RunLoop
//If no input sources or timers are attached to the run loop, this method exits immediately
//infinite loop that processes data from the run loop’s input sources and timers.
//calls RunLoop.run(mode:.default before:)
customRunLoop.run()
//------
//create custom mode
let customRunLoopMode = RunLoop.Mode("customeMode")
//2. A run loop must have at least one input source or timer to monitor
//Will be called when previous RunLoop.run() is done(no input sources or timers) - exit from loop
let timer2 = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
//"customeMode" mode
}
customRunLoop.add(timer2, forMode: customRunLoopMode)
//3. run RunLoop
let isInputSourcesOrTimers = customRunLoop.run(mode: customRunLoopMode, before: Date.distantFuture)
}
thread.start()
let customObserver = CFRunLoopObserverCreateWithHandler(nil, CFRunLoopActivity.allActivities.rawValue , true, 0) { observer, activity in
switch (activity) {
case .entry:
break
case .beforeTimers:
break
case .beforeSources:
break
case .beforeWaiting:
break
case .afterWaiting:
break
case .exit:
break
case .allActivities:
break
default:
break
}
}
Swift
let runLoop = RunLoop.current
Obj-c
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
A run loop is an event processing loop that is used to continuously monitor and process input events and assign them to the corresponding targets for processing.

Understanding NSRunLoop

Can anyone explain for what is NSRunLoop? so as I know NSRunLoop is a something connected with NSThread right? So assume I create a Thread like
NSThread* th=[[NSThread alloc] initWithTarget:self selector:#selector(someMethod) object:nil];
[th start];
-(void) someMethod
{
NSLog(#"operation");
}
so after this Thread finishes his working right? why use RunLoops or where to use ? from Apple docs I have read something but its not clear for me, so please explain as simple as it possible
A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, mouse, timers, etc).
Each NSThread has its own run loop, which can be accessed via the currentRunLoop method.
In general, you do not need to access the run loop directly, though there are some (networking) components that may allow you to specify which run loop they will use for I/O processing.
A run loop for a given thread will wait until one or more of its input sources has some data or event, then fire the appropriate input handler(s) to process each input source that is "ready.".
After doing so, it will then return to its loop, processing input from various sources, and "sleeping" if there is no work to do.
That's a pretty high level description (trying to avoid too many details).
EDIT
An attempt to address the comment. I broke it into pieces.
it means that i can only access/run to run loop inside the thread
right?
Indeed. NSRunLoop is not thread safe, and should only be accessed from the context of the thread that is running the loop.
is there any simple example how to add event to run loop?
If you want to monitor a port, you would just add that port to the run loop, and then the run loop would watch that port for activity.
- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode
You can also add a timer explicitly with
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
what means it will then return to its loop?
The run loop will process all ready events each iteration (according to its mode). You will need to look at the documentation to discover about run modes, as that's a bit beyond the scope of a general answer.
is run loop inactive when i start the thread?
In most applications, the main run loop will run automatically. However, you are responsible for starting the run loop and responding to incoming events for threads you spin.
is it possible to add some events to Thread run loop outside the thread?
I am not sure what you mean here. You don't add events to the run loop. You add input sources and timer sources (from the thread that owns the run loop). The run loop then watches them for activity. You can, of course, provide data input from other threads and processes, but input will be processed by the run loop that is monitoring those sources on the thread that is running the run loop.
does it mean that sometimes i can use run loop to block thread for a time
Indeed. In fact, a run loop will "stay" in an event handler until that event handler has returned. You can see this in any app simply enough. Install a handler for any IO action (e.g., button press) that sleeps. You will block the main run loop (and the whole UI) until that method completes.
The same applies to any run loop.
I suggest you read the following documentation on run loops:
https://developer.apple.com/documentation/foundation/nsrunloop
and how they are used within threads:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
Run loops are what separates interactive apps from
command-line tools.
Command-line tools are launched with parameters, execute their command, then exit.
Interactive apps wait for user input, react, then resume waiting.
From here
They allow you to wait till user taps and respond accordingly, wait till you get a completionHandler and apply its results, wait till you get a timer and perform a function. If you don't have a runloop then you can't be listening/waiting for user taps, you can't wait till a network call is happening, you can't be awoken in x minutes unless you use DispatchSourceTimer or DispatchWorkItem
Also from this comment:
Background threads don't have their own runloops, but you can just add
one. E.g. AFNetworking 2.x did it. It was tried and true technique for
NSURLConnection or NSTimer on background threads, but we don't do this
ourselves much anymore, as newer APIs eliminate the need to do so. But
it appears that URLSession does, e.g., here is simple request, running [see the left panel of the image]
completion handlers on the main queue, and you can see it has a run
loop on background thread
Specifically about: "Background threads don't have their own runloops". The following timer fails to fire for an async dispatch:
class T {
var timer: Timer?
func fireWithoutAnyQueue() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in
print("without any queue") // success. It's being ran on main thread, since playgrounds begin running from main thread
})
}
func fireFromQueueAsnyc() {
let queue = DispatchQueue(label: "whatever")
queue.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
print("from a queue — async") // failed to print
})
}
}
func fireFromQueueSnyc() {
let queue = DispatchQueue(label: "whatever")
queue.sync {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
print("from a queue — sync") // success. Weird. Read my possible explanation below
})
}
}
func fireFromMain() {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { (_) in
print("from main queue — sync") //success
})
}
}
}
I think the reason the sync block also runs is because:
sync blocks usually just get executed from within their source queue. In this example, source queue is main queue, the whatever queue is the destination queue.
To test that I logged RunLoop.current inside every dispatch.
The sync dispatch had the same runloop as main queue. While the RunLoop within the async block was a different instance from the others. You might be thinking how why does RunLoop.current return a different value. Isn't it a shared value!? Great question! Read further:
IMPORTANT NOTE:
The class property current is NOT a global variable.
Returns the run loop for the current thread.
It's contextual. It's visible only within the scope of the thread ie Thread-local storage. For more on that see here.
This is a known issue with timers. You don't have the same issue if you use DispatchSourceTimer
RunLoops are a bit of like a box where stuff just happens.
Basically, in a RunLoop, you go to process some events and then return. Or return if it doesn't process any events before the timeout is hit.
You can say it as similar to asynchronous NSURLConnections, Processing data in the background without interfering your current loop and but at the same time, you require data synchronously.
Which can be done with the help of RunLoop which makes your asynchronous NSURLConnection and provides data at calling time.
You can use a RunLoop like this:
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) {
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
}
In this RunLoop, it will run until you complete some of your other work and set YourBoolFlag to false.
Similarly, you can use them in threads.
Hope this helps you.
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
From here
The most important feature of CFRunLoop is the CFRunLoopModes. CFRunLoop works with a system of “Run Loop Sources”. Sources are registered on a run loop for one or several modes, and the run loop itself is made to run in a given mode. When an event arrives on a source, it is only handled by the run loop if the source mode matches the run loop current mode.
From here
iOS RunLoop
RunLoop(EventLoop, Looper) is an implementation of EventLoop (event processing loop) pattern. It is based on NSRunLoop (which a wrapper of CFRunLoopRef)
Official doc
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
Single thread can have single RunLoop in a single mode. Only events with this mode will be processed all others will be waiting when RunLoop will be started at that mode
RunLoop is a mechanism (based on loop(for, while)) which move a scheduled task(e.g Callback Queue) to a thread(thread stack). RunLoop works(event processing loop) when Thread Stack is empty.
event processing loop is when RunLoop between .entry and .exit. During it RunLoop handles all scheduled task in specific mode. All others modes with their own Queues will be managed after
Application by default has a main thread with RunLoop(main loop). In other cases you should create it manually
main run loop is responsible for draining the main queue in an app.
//Run loop for the current thread
RunLoop.current
//Run loop of the main thread.
RunLoop.main
Mode
A run loop mode is a collection of input sources and timers to be monitored and a collection of run loop observers to be notified.
modes:
default - used by default
tracking - for example when you scroll UITableView scrollViewDidScroll
common(is a pseudo mode like [default, tracking])
<custom> - you are able to create your own mode
//current input mode
RunLoop.current.currentMode
For example:
UIView.draw(_ rect:), button action... uses default mode
NSObject.perform(_:with:afterDelay:) uses default mode
DispatchQueue.main.async uses common mode
Timer.scheduledTimer uses default mode. That is why when UI scrolling occurring(in tracking mode) your timer is not fired(in default mode). To fix it use common mode - RunLoop.main.add(timer, forMode: .common)
Combine RunLoop.main vs DispatchQueue.main(.receive(on:, options:)). RunLoop.main uses RunLoop.perform(_:) which uses default mode, DispatchQueue.main uses DispatchQueue.main.async which uses common mode
input sources and timers
Run loop receives events:
Input sources - asynchronous events(as fired) messages
Port-based - from another thread or process. signaled automatically by the kernel
Custom Input Sources - user-initiated events - user actions, network events. must be signaled manually from another thread
performSelector: onThread
Timer sources - synchronous events(at specific time) timers
They can be added to several modes
observers
monitor RunLoop's state changes
Create RunLoop
create new thread, setup RunLoop and start the thread
create RunLoop
RunLoop.current
A run loop must have at least one input source or timer to monitor
RunLoop.add(_ timer: Timer, forMode mode: RunLoop.Mode)
RunLoop.add(_ aPort: Port, forMode mode: RunLoop.Mode)
run RunLoop
RunLoop.run()
let thread = Thread {
//1. create RunLoop
//create a new one or return existing run loop for current thread
//use RunLoop.current instead of RunLoop()
let customRunLoop = RunLoop.current
//add observer for current RunLoop for cpecufic mode
CFRunLoopAddObserver(CFRunLoopGetCurrent(), customObserver, CFRunLoopMode.commonModes)
//2. A run loop must have at least one input source or timer to monitor
let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
//.default mode
}
customRunLoop.add(timer, forMode: .default)
//3. run RunLoop
//If no input sources or timers are attached to the run loop, this method exits immediately
//infinite loop that processes data from the run loop’s input sources and timers.
//calls RunLoop.run(mode:.default before:)
customRunLoop.run()
//------
//create custom mode
let customRunLoopMode = RunLoop.Mode("customeMode")
//2. A run loop must have at least one input source or timer to monitor
//Will be called when previous RunLoop.run() is done(no input sources or timers) - exit from loop
let timer2 = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
//"customeMode" mode
}
customRunLoop.add(timer2, forMode: customRunLoopMode)
//3. run RunLoop
let isInputSourcesOrTimers = customRunLoop.run(mode: customRunLoopMode, before: Date.distantFuture)
}
thread.start()
let customObserver = CFRunLoopObserverCreateWithHandler(nil, CFRunLoopActivity.allActivities.rawValue , true, 0) { observer, activity in
switch (activity) {
case .entry:
break
case .beforeTimers:
break
case .beforeSources:
break
case .beforeWaiting:
break
case .afterWaiting:
break
case .exit:
break
case .allActivities:
break
default:
break
}
}
Swift
let runLoop = RunLoop.current
Obj-c
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
A run loop is an event processing loop that is used to continuously monitor and process input events and assign them to the corresponding targets for processing.

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