IOS, NotificationQueue call wrapped in DisplaceQueue.global does not fire - ios

I am trying to have my notification targets invoke in the background thread. The documentation asserts the targets will be invoked on the tread where the notification was enqueued.
If I wrap the enqueuing inside the DispatchQueue that runs in the background then the target is never invoked. If I remove the DispatchQueue then the targets are invoked, on the main thread of course.
for aLocation in locations {
// add it to the queue
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
let notify = Notification(name: MyLocationManager.queueName(), object:aLocation)
NotificationQueue.default.enqueue(notify,
postingStyle: NotificationQueue.PostingStyle.asap,
coalesceMask:NotificationQueue.NotificationCoalescing(rawValue: 0),
forModes: nil)
}
}

The short answer would be: NotificationQueues need a run loop
The long version:
The documentation asserts the targets will be invoked on the tread where the notification was enqueued.
This is not entirely true. The documentation asserts this fact only for Notification Centers but not for NotificationQueues
Here's what the NotificationQueue documentation has to say about it:
Whereas a notification center distributes notifications when posted, notifications placed into the queue can be delayed until the end of the current pass through the run loop or until the run loop is idle.
Then you have the enqueue function's 4th parameter:
modes:
The list of modes the notification may be posted in. The notification queue will only post the notification to its notification center if the run loop is in one of the modes provided in the array. May be nil, in which case it defaults to NSDefaultRunLoopMode.
Combine all this with the fact that GCD wont create a run loop on its global concurrent queues, unless somebody explicitly runs one on them, then we have this behaviour where the notification queue does not fire.

Related

beginBackgroundTask expirationHandler never called

I'm trying to complete a task in the background using UIApplication.shared.beginBackgroundTask but for some reason the expirationHandler is never called. The task I'm trying to complete is a video export from photo library but sometimes the export cannot be completed in time while the user is using the app in the foreground.
This is the code I'm using :
func applicationDidEnterBackground(_ application: UIApplication) {
if backgroundTask == .invalid && UploadQueue.instance.hasMoreWork() {
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "ExportQueue") {
NSLog("DriveLog - System is requesting end. Still more work to do ...")
self.endBackgroundTask()
}
print("Invalid? \(backgroundTask == .invalid)")
NSLog("DriveLog - Starting background task: %i", backgroundTask.rawValue)
}
}
func endBackgroundTask() {
NSLog("DriveLog - End called")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
I'm also calling :
(UIApplication.shared.delegate as! AppDelegate).endBackgroundTask()
during my task if I finish it earlier.
However I never see my expirationHandler being called in the log.
I have also tried putting beginBackgroundTask when starting the task in foreground but I get a warning message about task expiration while being in foreground.
You have not understood what the expiration handler is. It is called only if your time expires. Hence the name.
As soon as you call begin, start your task in the next line (not in the expiration handler). And when you are finished, call end.
You thus need to end the background task in two places: in the expiration handler, and outside it after actually performing your task.
It is very important to call end in both places, because if you fail to do so, the system will decide that you are a bad citizen and will never grant you any extra background time at all.
So, this is the diagram of the flow you need to construct:
Also note that this has nothing to do with UIBackgroundModes. That's a totally different mechanism.
matt's answer covers everything. I'm just going to try to give the same answer in different words because your edit suggests that matt's answer wasn't clear to you. (Read it again, though, it really does cover everything I'm going to say here, just in different words.)
You should not call beginBackgroundTask in applicationDidEnterBackground. You call it when you start whatever task you want time for. In your example that's going to be somewhere inside of UploadQueue. You don't call beginBackgroundTask when going into the background. You call it when you're starting a task that you would like to finish even if you go into the background.
Background tasks generally do not belong to the UIAppDelegate. They belong to the thing that creates the task (in your case: UploadQueue). You can create all the background tasks you want. They cost almost nothing. It's not just one "I want background" at the app level. Read matt's flow chart closely.
It's unclear from your question why you expect the expiration handler to be called. Do you expect your task to task to take so long that the OS forces you to stop it? That's what the expiration handler is for. If you've built your system correctly, it should rarely be called. Your task should end long before it's expired.
For full docs on how to do this, see Extending Your App's Background Execution Time. In particular note the caution:
Don’t wait until your app moves to the background to call the beginBackgroundTask(withName:expirationHandler:) method. Call the method before performing any long-running task.

Order of operations in runloop on iOS

What is the order of operations on iOS?
I'm thinking sepcifically about timing of
setNeedsLayout and layoutSubviews
setNeedsDisplay and drawRect
touch recognition
[NSTimer scheduledTimerWithTimeInterval:0.000001 tar(...)]
dispatch_async(dispatch_get_main_queue(), ^{ /* code */}
As an example of an answer I would like to receive it could be in this format:
dispatch_async on main Happens before the next runcycle
drawRect Happens at the end of the runcycle
(Parts of this are copied from my answer to a similar question.)
It turns out that the run loop is complicated, and a simple question like “Does drawRect: happen at the end of the runcycle?” doesn't have a simple answer.
CFRunLoop is part of the open-source CoreFoundation package, so we can take a look at exactly what it entails. The run loop looks roughly like this:
while (true) {
Call kCFRunLoopBeforeTimers observer callbacks;
Call kCFRunLoopBeforeSources observer callbacks;
Perform blocks queued by CFRunLoopPerformBlock;
Call the callback of each version 0 CFRunLoopSource that has been signaled;
// Touch events are a version 0 source in iOS 8.0.
// CFSocket is a version 0 source.
if (any version 0 source callbacks were called) {
Perform blocks newly queued by CFRunLoopPerformBlock;
}
if (I didn't drain the main queue on the last iteration
AND the main queue has any blocks waiting)
{
remove all blocks from the main queue
execute all the blocks just removed from the main queue
} else {
Call kCFRunLoopBeforeWaiting observer callbacks;
// Core Animation uses a BeforeWaiting observer to perform layout and drawing.
Wait for a CFRunLoopSource to be signalled
OR for a timer to fire
OR for a block to be added to the main queue;
Call kCFRunLoopAfterWaiting observer callbacks;
if (the event was a timer) {
call CFRunLoopTimer callbacks for timers that should have fired by now
} else if (event was a block arriving on the main queue) {
remove all blocks from the main queue
execute all the blocks just removed from the main queue
} else {
look up the version 1 CFRunLoopSource for the event
if (I found a version 1 source) {
call the source's callback
}
// Interface orientation changes are a version 1 source in iOS 8.0.
}
}
Perform blocks queued by CFRunLoopPerformBlock;
}
Core Animation registers a kCFRunLoopBeforeWaiting observer with an order of 2000000 (although that is not documented; you can figure it out by printing [NSRunLoop mainRunLoop].description). This observer commits the current CATransaction, which (if necessary) performs layout (updateConstraints and layoutSubviews) and then drawing (drawRect:).
Note that the run loop can evaluate the true in while(true) twice before executing BeforeWaiting observers. If it dispatches timers or a version 1 source, and that puts block on the main queue, the run loop will go around twice before calling the BeforeWaiting observers (and it will dispatch version 0 sources both times).
The system uses a mixture of version 0 sources and version 1 sources. In my testing, touch events are delivered using a version 0 source. (You can tell by putting a breakpoint in a touch handler; the stack trace contains __CFRunLoopDoSources0.) Events like entering/leaving foreground are dispatched through CFRunLoopPerformBlock, so I don't know what kind of source really provides them. Interface orientation changes are delivered through a version 1 source. CFSocket is documented to be a version 0 source. (It's likely that NSURLSession and NSURLConnection use CFSocket internally.)
Note that the run loop is structured so only one of these branches happens on each iteration:
Ready timers fire, or
Blocks on dispatch_get_main_queue() run, or
A single version 1 source is dispatched to its callback.
After that, any number of version 0 sources can call their callbacks.
So:
Layout always happens before drawing, if both are pending when the Core Animation observer runs. The CA observer runs after timers, main queue blocks, or the external event callback have run.
The main GCD queue has seniority over timers and version 1 sources, if the run loop didn't drain the main queue on the prior turn of the loop.
Timers have seniority over the main queue and version 1 sources, should all three be ready.
The main queue has seniority over version 1 sources, should both be ready.
Also remember that you can request immediate layout at any time using layoutIfNeeded.
One task after the other is added to the runloop from various sources; the runloop will execute the oldest task on the runloop and not start another task until the call for that task returns.
Handling of user interaction
UI Components call setNeedsLayout and setNeedsDisplay if they need an update
Layouting is done using layoutSubviews (called indirectly by layoutSublayers)
Painting is done using drawRect and drawInContext:
dispatch_async call is performed
Your timer with 0.000001 seconds delay could be executed before or after the dispatch_async. Difficult to say.
1 and 2 are actually mixed because it's mostly the user interaction causing changes in the UI by calling setNeedsLayout and setNeedsDisplay somewhere.
The order of 1, 2, 3 and 4 is well-defined. 5 should also always happen afterwards. NSTimer depends on various circumstances - you should not rely that it's called before or after the dispatch_async call but it most likely will be executed after the painting is done.

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.

Posting NSNotification on the main thread

I found the following code snippet which allows NSNotification to be posted on the main thread from any background thread. I would like to know if this is a safe and acceptable practice please?
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"ImageRetrieved"
object:nil
userInfo:imageDict];
});
Yes you can.
Generally you want the NSNotifications to be sent on the main , especially if they trigger UI activities like dismissing a modal login dialog.
Delivering Notifications To Particular Threads
Regular notification centers deliver notifications on the thread in
which the notification was posted. Distributed notification centers
deliver notifications on the main thread. At times, you may require
notifications to be delivered on a particular thread that is
determined by you instead of the notification center. For example, if
an object running in a background thread is listening for
notifications from the user interface, such as a window closing, you
would like to receive the notifications in the background thread
instead of the main thread. In these cases, you must capture the
notifications as they are delivered on the default thread and redirect
them to the appropriate thread.
Yes
This is - you are getting into the main thread and posting your notification. Can't get any safer than that.
YES
Swift 2 syntax
dispatch_async(dispatch_get_main_queue()) {
NSNotificationCenter.defaultCenter().postNotificationName("updateSpinner", object: nil, userInfo: ["percent":15])
}
Swift 3 syntax
DispatchQueue.main.async {
NotificationCenter.default.post(name: "updateSpinner", object: nil, userInfo: ["percent":15])
}
Somewhere along the line this became possible with:
addObserver(forName:object:queue:using:)
which is here, but the whole point is the queue object.
The operation queue to which block should be added. If you pass nil,
the block is run synchronously on the posting thread.
So how do you get the queue that corresponds to the main runloop?
let mainQueue = OperationQueue.main
Note: this is when you are subscribing to notifications, so you do it once and you're done. Doing it on every single call is terribly redundant.

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.

Resources