Understanding Nsrunloop

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

  1. The change you see when you use run or runUntilDate: instead of runMode:beforeDate: is expected. The documentation for runMode: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 send performSelector:..., 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, your while loop never gets a chance to test exitThread.

  2. Your attempt to use CFRunLoopStop is a good idea, but unfortunately, the documentation for run 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 make run return.

    Instead, you can drop to a lower level and use CFRunLoopRun to run the run loop, because CFRunLoopStop is documented to make that function return:

    This function forces rl to stop running and return control to the function that called CFRunLoopRun or CFRunLoopRunInMode 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



Leave a reply



Submit