3

I bought the Big Nerd Ranch Guide for Objective-C, and there is something about NSRunLoop I can't figure out.

Here's a chunk of code from the book:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 
                                                  target:logger
                                                selector:@selector(updateLastTime:)
                                                userInfo:nil
                                                 repeats:YES];
[[NSRunLoop currentRunLoop] run];

My question is, why do I need to put an NSRunLoop for the NSTimer object to be processed? And why does it need to be at the end, and not the beginning?

Why it is not like the other functions or object's methods where I simply have to call a function for it to be processed and logged into the console?

I'm really trying figure out every logic of every detail here.

jscs
  • 63,694
  • 13
  • 151
  • 195
ocampeau
  • 157
  • 1
  • 8
  • 1
    This may be helpful: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html – quellish Dec 09 '15 at 02:23

1 Answers1

6

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.)

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Nice answer. Is not the GCD main queue scheduled off the run loop? So the run loop is still fundamental post 10.6. – CRD Dec 09 '15 at 21:40
  • @CRD It's absolutely fundamental. "It's still the workhorse of most apps even if you never think about it." But in most cases GCD is a better solution at the app layer. And much more friendly to Swift. – Rob Napier Dec 10 '15 at 00:39
  • Apologies Rob, I could have been clearer. Its not that it is "still the workhorse" surely, but that it will remain central as the whole GUI/event model is built around it, and it runs the GCD main queue. GCD is not used directly to handle mouse, keyboard, window etc. events. Moving away from that will be a big change, so I expect the run loop is going nowhere quickly. – CRD Dec 10 '15 at 02:31
  • Certainly not. But for most things that especially iOS developers do, there is seldom much reason to deal with it. It comes up a little more often on Mac. But you can write serious programs today and never directly call a run loop method or create an NSTimer. It's hard to build a serious program today and never use GCD. Like objc_retain, it is something intermediate and advanced devs must understand, but it doesn't *directly* come up that often. – Rob Napier Dec 10 '15 at 02:55
  • Very interesting read, I wonder how does this for(;;) not ramp CPU usage too high? – Woodstock Jul 16 '20 at 20:50
  • 1
    @Woodstock It's not a tight, empty loop. It blocks when there is no message to process. If you look through the code, you'll see that on Darwin it calls mach_msg to yield to the OS, waiting for a message. On WIN32 it uses MsgWaitForMultipleObjects. The point of the `for(;;)` is that it's an infinite loop, not that it spends all of its time on the CPU. – Rob Napier Jul 16 '20 at 22:30
  • 1
    @Rob thanks very much for the reply. Great to learn about this stuff. Makes perfect sense. It’s funny, we learn academically never to sleep a thread, or use goto etc. They are no nos in college. Yet sleeping is done in low level kernel code :) – Woodstock Jul 17 '20 at 11:42