Understanding NSRunLoop
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
What does NSRunLoop do?
The example you've shown is a Cocoa idiom for creating a thread that will continue to run after the method -threadProc
exits. Why?
Because:
- the
NSRunLoop
instance you've created has at least one input source ([NSMachPort port]
) - you've explicitly started the run loop with
runMode:beforeDate
Without adding an input source and explicitly starting the run loop, the thread would terminate.
Parenthetically, although run loops are still vital for managing events and certain asynchronous tasks, I would not view NSThread
as the default way of architecting most asynchronous work in a Cocoa app nowadays. GCD is a far cleaner way of encapsulating background work.
EDIT:
Submitting work to a serial queue in GCD:
@interface Foo : NSObject
@end
@implementation Foo {
dispatch_queue_t _someWorkerQueue;
}
- (id)init {
self = [super init];
if( !self ) return nil;
_someWorkerQueue = dispatch_queue_create("com.company.MyWorkerQueue", 0);
return self;
}
- (void)performJob {
dispatch_async(_someWorkerQueue, ^{
//do some work asynchronously here
});
dispatch_async(_someWorkerQueue, ^{
//more asynchronous work here
});
}
@end
Why do I need an NSRunLoop to run a timer?
From the early days of what would become Cocoa, when dinosaurs roamed the Earth, rocks were soft, and NeXT workstations were new, up until 10.6 came out, the most common type of multitasking was the run loop. It's cooperative multitasking. There are no threads. There is no preemptive scheduler or kernel. There are no context switches. There's just a big run loop that says "what needs doing now?" and runs it. And when that thing completes, it waits for the next thing that needs doing and runs that. It literally is a big while(true)
loop. Well, technically the line of code is:
for (;;) { ... }
You can see for yourself in CFRunLoop.c. Look for __CFRunLoopRun
.
NSTimer
was invented in those days. All it does it make a note in the runloop telling it "when this time passes, then please do this." (It's a tiny bit more complicated than that because it uses mach ports, look for __CFRunLoopTimerSchedule
in the same file for details, but basically that's the idea.)
So the point is, there's no magic. There's just a big for(;;)
loop that processes this stuff. Something has to run it. And when you start it (with run
), it doesn't return. It's an infinite loop. There is no "background." There are no other threads. And that's why you need to do things in the order BNR tells you to. Otherwise your next line of code wouldn't run.
Of course in iOS apps and OS X GUI apps, you don't usually have to do this yourself. The run loop gets created for you during program startup, and the whole main thread lives inside of it. It's the thing that calls you most of the time. You don't call it. But if you're on a thread other than the main thread, and you want to use run loop functionality, you're going to have to run it yourself.
Today, a lot of things are done with GCD rather than run loops. That's the "until 10.6 came out" that I mentioned. It really changed the Cocoa world. But a huge amount of Cocoa still relies on the run loop, and it's still the workhorse of most apps even if you never think about it.
In most cases today, if you're having to create a runloop in order to use NSTimer
, you shouldn't be using NSTimer
. Just use dispatch_after
. In fact, that's what I usually recommend most of the time today even if you do have a runloop.
(And you should definitely read the link @quelish gives in the comments. It is the definitive word on run loops.)
Understanding the Objective-C event loop
These functions will cause all messages to be logged to a file in /tmp, based on the PID of the process. Good on simulator, but not on an iDevice.
// Start logging all messages
instrumentObjcMessageSends(YES);
// Stop logging all messages
instrumentObjcMessageSends(NO);
Detail about nsrunloop
a runloop is basically an extended while loop that works like C select call.
it is responsible for getting events from its sources and dispatching those.
mouse clicks, window moves, timers, stream events, ...... anything can be a runloop source.
thats the gist. read more about it in the countless dupes this has on SO or in the apple docs :)
NSRunLoop- Clarification needed
The change you see when you use
run
orrunUntilDate:
instead ofrunMode:beforeDate:
is expected. The documentation forrunMode:beforeDate:
says this:it returns after either the first input source is processed or
limitDate
is reached.There is an input source responsible for handling the
performSelector:...
requests. So when you sendperformSelector:...
, the run loop processes an input source and then returns.On the other hand, the documentation for
run
says this:it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
So after the run loop processes the input source for your
performSelector:...
request, it waits for another input source to be ready. It doesn't return. Since it doesn't return, yourwhile
loop never gets a chance to testexitThread
.Your attempt to use
CFRunLoopStop
is a good idea, but unfortunately, the documentation forrun
says this:If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop.
So you can't rely on
CFRunLoopStop
to makerun
return.Instead, you can drop to a lower level and use
CFRunLoopRun
to run the run loop, becauseCFRunLoopStop
is documented to make that function return:This function forces
rl
to stop running and return control to the function that calledCFRunLoopRun
orCFRunLoopRunInMode
for the current run loop activation.So try this to run your run loop:
while(!exitThread) {
NSLog(@"Thread did some work");
CFRunLoopRun([NSRunLoop currentRunLoop].getCFRunLoop);
}
NSRunLoop Implementation
Generally speaking, there are lots of system calls which will block. That is, they will cause the kernel to suspend the thread and not give it CPU time until some specific event or state change occurs. Things like kevent()
, poll()
, select()
, read()
on a non-plain-file descriptor, etc.
With regard to NSRunLoop
specifically, it's built around CFRunLoop
, which is open source: https://www.opensource.apple.com/source/CF/CF-855.11/CFRunLoop.c. It uses the Mach API on OS X and iOS. In particular, it calls mach_msg()
to wait for input sources (in the form of Mach ports) to fire (receive messages) or timers to elapse.
Related Topics
Background-Size: Cover Not Working on iOS
Draw Segments from a Circle or Donut
iOS Detect If User Is on an iPad
Uiview and Initwithframe and a Nib File. How to Get the Nib File Loaded
How to Recognize Swipe in All 4 Directions
iOS (Iphone, iPad, Ipodtouch) View Real-Time Console Log Terminal
iOS 7 Status Bar with Phonegap
Auto Layout Constraints Issue on iOS7 in Uitableviewcell
Changing Root View Controller of a iOS Window
How to Create Percentage of Total Width Using Autolayout
How to Get List of Available Bluetooth Devices