iOS app runs for only 1 second in background - ios

I have 2 classes, ViewController class, and Worker. All the code that I need to run in the background is in the Worker class.
My ViewController looks something like this:
- (void)viewDidLoad {
//create an instance of 'Worker'
}
- buttonClick {
//call the 'manager' method in the worker instance that was just created (do this method as a background thread)
}
My Worker class looks something like this:
- (void)manager {
//call 'repeat' method as a background thread
}
- (void)repeat {
//call 'innerWorker' method as a background thread
}
- (void)innerWorker {
//do work
}
The repeat method needs to get run every second.
I've tried the 2 following ways to make the repeat method run every second.
Method 1 :
timerObj = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(repeatMethod) userInfo:nil repeats:YES];
Method 2:
Putting this code at the end of repeat:
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, q_background, ^(void){
[self repeatMethod];
});
Both of these 2 methods work fine as long as the app is in the foreground but the moment I press the home button, the repeat method runs one last time, but does not call the innerWorker method and then the app is suspended. I know this by using NSLogs all over the place.
I realize Method 2 is a bit of a hack but that's fine as this is an internal app that I wont be publishing.
All the methods are called as background threads using this code: eg:
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(q_background, ^{
[self repeatMethod];
});
I'm new to iOS so maybe I'm missing something small here. I just want my app to keep running in the background. Please help me.

First of all: the dispatch_queue priority has nothing to do with running in background ... it is the priority in the queue.
Here are the info:
DISPATCH_QUEUE_PRIORITY_HIGH Items dispatched to the queue will run at high priority, i.e. the queue will be scheduled for execution before any default priority or low priority queue.
DISPATCH_QUEUE_PRIORITY_DEFAULT Items dispatched to the queue will run at the default priority, i.e. the queue will be scheduled for execution after all high priority queues have been scheduled, but before any low priority queues have been scheduled.
DISPATCH_QUEUE_PRIORITY_LOW Items dispatched to the queue will run at low priority, i.e. the queue will be scheduled for execution after all default priority and high priority queues have been scheduled.
DISPATCH_QUEUE_PRIORITY_BACKGROUND Items dispatched to the queue will run at background priority, i.e. the queue will be scheduled for execution after all higher priority queues have been scheduled and the system will run items on this queue on a thread with background status as per setpriority(2) (i.e. disk I/O is throttled and the thread's scheduling priority is set to lowest value).
For "real" background operations check this:
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
The answer to your question depends on what you want to do.

Only the following types of apps, with the appropriate plist flag set, can execute a background process while the app is still in the foreground:
Apps that play audible content to the user while in the background,
such as a music player app
Apps that record audio content while in the background
Apps that keep users informed of their location at all times, such
as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
If your app falls outside of one of those categories, and will go through app store approval, you will need to devise another strategy for doing the background work you desire to preform.
More info can be found here:
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW3

Related

Suspending serial queue

Today i've tried following code:
- (void)suspendTest {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_BACKGROUND, 0);
dispatch_queue_t suspendableQueue = dispatch_queue_create("test", attr);
for (int i = 0; i <= 10000; i++) {
dispatch_async(suspendableQueue, ^{
NSLog(#"%d", i);
});
if (i == 5000) {
dispatch_suspend(suspendableQueue);
}
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"Show must go on!");
dispatch_resume(suspendableQueue);
});
}
The code starts 10001 tasks, but it should suspend the queue from running new tasks halfway for resuming in 6 seconds. And this code works as expected - 5000 tasks executes, then queue stops, and after 6 seconds it resumes.
But if i use a serial queue instead of concurrent queue, the behaviour is not clear for me.
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
In this case a random number of tasks manage to execute before suspending, but often this number is close to zero (suspending happens before any tasks).
The question is - Why does suspending work differently for serial and concurrent queue and how to suspend serial queue properly?
As per its name, the serial queue performs the tasks in series, i.e., only starting on the next one after the previous one has been completed. The priority class is background, so it may not even have started on the first task by the time the current queue reaches the 5000th task and suspends the queue.
From the documentation of dispatch_suspend:
The suspension occurs after completion of any blocks running at the time of the call.
i.e., nowhere does it promise that asynchronously dispatched tasks on the queue would finish, only that any currently running task (block) will not be suspended part-way through. On a serial queue at most one task can be "currently running", whereas on a concurrent queue there is no specified upper limit. edit: And according to your test with a million tasks, it seems the concurrent queue maintains the conceptual abstraction that it is "completely concurrent", and thus considers all of them "currently running" even if they actually aren't.
To suspend it after the 5000th task, you could trigger this from the 5000th task itself. (Then you probably also want to start the resume-timer from the time it is suspended, otherwise it is theoretically possible it will never resume if the resume happened before it was suspended.)
I think the problem is that you are confusing suspend with barrier. suspend stops the queue dead now. barrier stops when everything in the queue before the barrier has executed. So if you put a barrier after the 5000th task, 5000 tasks will execute before we pause at the barrier on the serial queue.

Is it safe to have a timer dispatch source scheduled every second?

I have this timer created using GDC. It will call a method every 1 second. Is it safe to have this timer alive during the whole time, even in background?
self.theTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(self.theTimer, DISPATCH_TIME_NOW, (1.0) * NSEC_PER_SEC, 0.25 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.theTimer, ^{
[self awakeAndProcess];
});
// Start the timer
dispatch_resume(self.theTimer);
The method "awakeAndProcess" has a "consumer" behavior where it checks a data queue and tries to send an HTTP request. So it is constantly checking if there are messages to be sent
You are better off pausing the timer when going to background to conserve battery because the awakeAndProcess seems to be a network call. But if you are in background then all your tasks are suspended anyways so shouldn't be a problem. When in foreground its better to wait for the previous awakeAndProcess call to finish before you trigger the next call. Otherwise you might end up with lot of awakeAndProcess calls being batched together. If awakeAndProcess is not reentrant then it can cause havoc in your code.
You are better off suspending the timer after the awakeAndProcess and then call resume after the awakeAndProcess is fully complete.
If you do this then its safe to use your approach.

Which queue is used for -[NSObject performSelector:withObject:afterDelay]?

I recently ran into an issue where deferred selectors weren't firing (an NSTimer and methods called with performSelector:withObject:afterDelay).
I've read Apple's documentation, and it does mention in the special considerations area,
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly. One common context where you might call this method and end up registering with a runloop that is not automatically run on a regular basis is when being invoked by a dispatch queue. If you need this type of functionality when running on a dispatch queue, you should use dispatch_after and related methods to get the behavior you want.
This makes perfect sense, except for the runloop of its current context part. I found myself confused regarding which runloop it's actually going to. Would it be the thread's main runloop that processes all events, or could it be a different one without our knowledge?
For instance, if I hit a breakpoint before calling performSelector inside a block that is being called as a CoreAnimation completion block, the debugger shows execution is on the main thread. However, calling performSelector:withObject:afterDelay never actually runs the selector. This makes me think that call is effectively registering with the runloop associated with the CoreAnimation framework, so regardless of the performSelector call being executed on the main thread, if the CoreAnimation doesn't poll its runloop, the operation isn't executed.
Replacing this call inside that block with performSelectorOnMainThread:WithObject:waitUntilDone fixes the problem, but I've had a hard time convincing a colleague that this is the root cause.
Update: I was able to trace back the origin of the issue to a UIScrollViewDelegate callback. It makes sense that when a UI delegate callback is invoked that the main runloop would be in UITrackingRunLoopMode. But at that point, the handler will queue a block on a background queue and from there execution will jump across a few other queues, eventually coming back to the main runloop. The catch is that when it comes back to the main runloop, it's still in UITrackingRunLoopMode. I think that the main runloop should have come out of UITracking mode when the delegate method was completed, but when execution gets back to main runloop, it's still in that mode. Deferring the code that kicks off the background queueing of the job from the UIScrollViewDelegate method fixes the problem, e.g [self performSelector:#selector(sendTaskToBackQueue) withObject:nil afterDelay:0 inModes:#[NSDefaultRunLoopMode]]. Is it possible that the runloop mode that is used when the background task is queued back to the main thread is dependent on the mode the runloop was in when it queued the background task?
Essentially, the only change was going from this...
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Currently in UITrackingRunLoopMode
dispatch_async(someGlobalQueue, someBlock);
// Block execution hops along other queues and eventually comes back to main runloop and will still be in tracking mode.
}
to this
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Currently in UITrackingRunLoopMode
[self performSelector:#selector(backQueueTask) withObject:nil afterDelay:0 inModes:#[NSDefaultRunLoopMode]];
}
-(void)backQueueTask {
// Currently in NSDefaultRunLoopMode
dispatch_async(someGlobalQueue, someBlock);
// Hops along other queues and eventually comes back to main runloop and will still be in NSDefaultRunLoopMode.
// It's as if the runloop mode when execution returns was dependent on what it was when the background block was queued.
}
There is only one run loop per thread, so if you're on the main thread then you're also on the main run loop. However, a run loop can run in different modes.
There are a few things you can try to get to the bottom of the issue:
You can use +[NSRunLoop currentRunLoop] and +[NSRunLoop mainRunLoop] to verify that you're executing from the main thread and main run loop.
You can also use the current run loop directly with NSTimer to schedule a delayed perform-selector. E.g.:
void (^completionBlock)(BOOL) = ^(BOOL finished) {
NSCAssert([NSRunLoop currentRunLoop] == [NSRunLoop mainRunLoop], #"We're not on the main run loop");
NSRunLoop* runLoop = [NSRunLoop mainRunLoop];
// Immediate invocation.
[runLoop performSelector:#selector(someMethod) target:self argument:nil order:0 modes:#[NSDefaultRunLoopMode]];
// Delayed invocation.
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(someMethod) userInfo:nil repeats:NO];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
};
Those calls are essentially equivalent to -performSelector:withObject: and -performSelector:withObject:afterDelay:.
This allows you to confirm which run loop you're using. If you're on the main run loop and the delayed invocation doesn't run, it's possible that the main run loop is running in a mode that doesn't service timers in the default mode. For example, that can happen when a UIScrollView is tracking touch input.
-performSelector:withObject:afterDelay: doesn't schedule operations on a dispatch queue; it schedules it on the run loop of the current thread. Each thread has one run loop, but somebody has to run the run loop in order for it to execute the actions on it. So it all depends on what thread this code is run on.
If it is run on the main thread, the operation will be scheduled on the main thread's run loop. In event-based applications, UIApplicationMain is called in the main function, which runs a run loop on the main thread for the entire lifetime of the app.
If this is run on another thread that you created, then the operation will be put on that thread's run loop. But unless you explicitly run the thread's run loop, the operations scheduled on the run loop won't run.
If this is run on a GCD dispatch queue, it means it is running on some unknown thread. GCD dispatch queues manage threads internally in a way that is opaque to the user. Generally nobody would have run the run loop on such a thread, so operations scheduled on the run loop won't run. (Of course, you could explicitly run the run loop in the same place that you schedule the operation, but that would block the thread, and thus block the dispatch queue, which wouldn't make that much sense.)
performSelector:withObject:afterDelay this will call the selector on the thread that this function is called.
performSelectorOnMainThread:WithObject:waitUntilDon,this will make sure that the selector is called on main thread
What is run loop:
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.

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.

Resources