25

Is it possible to initialize a NSRunLoop without loading any NIB files (i.e., without calling NSApplicationMain())?

Thanks.

Jon Gauthier
  • 25,202
  • 6
  • 63
  • 69

8 Answers8

15

The solution is to invoke NSApplication manually. Create your app delegate first than replace the NSApplicationMain() call in main.m with the following:

AppDelegate * delegate = [[AppDelegate alloc] init];

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSApplication * application = [NSApplication sharedApplication];
[application setDelegate:delegate];
[NSApp run];

[pool drain];

[delegate release];

The delegate will be invoked when ready, without needing a nib

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
  • 1
    There's no reason one necessarily needs to use NSApplication (and thus introduce a dependency on AppKit); this can all be done with NSRunLoop. As such, while this is potentially useful information for someone, I think it's an inappropriate answer to this question. Down-voting for that reason. – lindes Apr 10 '12 at 10:07
  • I was using [[NSRunLoop currentRunLoop] run]; in my GUI-less application, but my application was going into not responding state. This method is working fine for me. – Parag Bafna Jun 10 '13 at 13:07
  • 4
    For ARC, remove [delegate release], [pool drain] and the NSAutoreleasePool, and put @autoreleasepool { } around all of the code. – Jarvix Mar 15 '14 at 00:39
15

In Swift, you can achieve this by appending the following line to the end of your main.swift:

NSRunLoop.currentRunLoop().run();  // Swift < 3.0
RunLoop.current.run();             // Swift >= 3.0

If you want to be able to stop the run loop you have to use the Core Foundation methods.

CFRunLoopRun(); // start

And you can stop it like this

CFRunLoopStop(CFRunLoopGetCurrent()); // stop
Chad Cache
  • 9,668
  • 3
  • 56
  • 48
12

Yes; you can write your own main method and run NSRunLoop without returning from NSApplicationMain.

Have a look at this link; this guy is using NSRunLoop in his main method, he is not loading NIB files though, but it should get you going with NSRunloops.

vike
  • 339
  • 6
  • 10
raziiq
  • 549
  • 11
  • 28
  • See also: the official [NSRunLoop documentation](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class) – lindes Apr 10 '12 at 10:09
8
// Yes.  Here is sample code (tested on OS X 10.8.4, command-line).
// Using ARC:
// $ cc -o timer timer.m -fobjc-arc -framework Foundation
// $ ./timer
//

#include <Foundation/Foundation.h>

@interface MyClass : NSObject
@property NSTimer *timer;
-(id)init;
-(void)onTick:(NSTimer *)aTimer;
@end

@implementation MyClass
-(id)init {
    id newInstance = [super init];
    if (newInstance) {
        NSLog(@"Creating timer...");
        _timer = [NSTimer scheduledTimerWithTimeInterval:1.0
            target:self
            selector:@selector(onTick:)
            userInfo:nil
            repeats:YES];
    }
    return newInstance;
}

-(void)onTick:(NSTimer *)aTimer {
    NSLog(@"Tick");
}
@end

int main() {
    @autoreleasepool {
        MyClass *obj = [[MyClass alloc] init];
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
rmcghee
  • 419
  • 4
  • 6
8

Follow the recommendations in the docs for [NSRunLoop run]:

BOOL shouldKeepRunning = YES;        // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate     distantFuture]]);
Chris
  • 39,719
  • 45
  • 189
  • 235
  • some more explanation is due - when I try it, the while() never exits before the end-date (and when that's set to distantFuture - it just never exists to check the shouldKeepRunning). I tried to remove everything from the runloop, to no avail. CFRunnLoopStop() crashes, and.... well... more explanation is due. this doesn't work as expected. – Motti Shneor Aug 05 '20 at 06:38
  • Hi, are you setting shouldKeepRunning = NO at some point in something that has been scheduled on your runloop? You must schedule things on the runloop before entering this while loop as well. Good luck! – Chris Aug 06 '20 at 23:22
5

Have a look at asynctask.m that runs an NSRunLoop manually to enable the use of asynchronous "waitForDataInBackgroundAndNotify" notifications.

http://www.cocoadev.com/index.pl?NSPipe

  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

   while(!terminated) 
   {
     //if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:100000]])
     if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) 
     {
         break;
     }
      [pool release];
      pool = [[NSAutoreleasePool alloc] init];
   }

   [pool release];
Parag Bafna
  • 22,812
  • 8
  • 71
  • 144
gregor
  • 51
  • 1
  • 1
0

Here is my take using only DispatchQueue. Most command line tools want to exit with a status. The background dispatch queue is concurrent.

import Foundation

var exitStatus: Int32 = 0

let background = DispatchQueue(label: "commandline", qos: .userInteractive, attributes: [], autoreleaseFrequency: .workItem)

background.async {
    var ii = 1
    while ii < CommandLine.arguments.count {
        
        process(file:CommandLine.arguments[ii])
        ii += 1
    }
}
background.async {
    exit(exitStatus)
}
RunLoop.current.run()
0

I just wrote a CLI app and I needed to run a runLoop to let NSWorkspace class works right and also to get notifications from NSWorkspace. I made it thanks this discussion, but I don't understand why most of the answers suggest to use a NSTimer so I would like to share my solution without it.

Because [NSRunLoop run] "blocks" the thread and also my CLI logic blocks the thread by waiting for stdin, in the main function I create a thread for my CLI app logic, then I runned the NSRunLoop:

int main(int argc, char *argv[]) {

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

  pthread_t thread;
  // current NSRunLoop passed to the thread
  err = pthread_create(&thread, NULL, run_thread, runLoop);
  if (err != 0) {
    return err;
  }

  [runLoop run];
  [pool release];
  
  // I'm not able to stop the NSRunLoop even with CFRunLoopStop
  // so you probably never reach this point.
  // However, for safety, here I wrote code for handling pthread_join

  return 0;
}

Then in my thread I do my CLI logic and, when I need to access to NSWorkspace API (that needs the NSRunLoop to work) I use the performBlock: method on NSRunLoop:

void *run_thread(void *data) {
  NSRunLoop *runLoop = (NSRunLoop *)data;
  FILE *input = stdin;
  FILE *output = stdout;

  // my CLI code that read from stdio
  // while (1) {
  //   fread( ... );
  //   ...

  // then, when I need, I schedule code to the NSRunLoop
  [runLoop performBlock:^{
    // call API that need the main runLoop to work
    // [[[NSWorkspace sharedWorkspace] notificationCenter] addObserverForName: ...];
    // ...
  }];

  // to stop the runLoop so the application I call exit inside the runLoop
  [runLoop performBlock:^{
    exit(0);
  }];

  // ...
}

To be more accurate I decided to read data (input) from my thread and work with output in the runLoop so to not have concurrency on resources.

cescobaz
  • 366
  • 3
  • 8