24
votes

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

Thanks.

7
Why do you want a run loop in a CLI app? - kennytm
why do you want to load nib files in a cli app? - bertolami
bertolami: I think he meant without. - Peter Hosey
@KennyTM: One reason one might want to do this is to use functionality such as CoreLocation (among various others) that requires a run loop to function, but doesn't inherently have any GUI needs. - lindes
@KennyTM - he may want to do something like make a NSURLConnection in main. Without any special handling, he could exit main before the program really completes due to delegates being invoked. - jww

7 Answers

15
votes

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
14
votes

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
12
votes

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.

8
votes
// 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;
}
8
votes

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]]);
5
votes

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];
0
votes

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