Reputation:
Is it possible to initialize a NSRunLoop
without loading any NIB files (i.e., without calling NSApplicationMain()
)?
Thanks.
Upvotes: 26
Views: 14999
Reputation: 366
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.
Upvotes: 0
Reputation: 61
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()
Upvotes: 0
Reputation: 549
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
.
Upvotes: 12
Reputation: 9859
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
Upvotes: 15
Reputation: 40641
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]]);
Upvotes: 9
Reputation: 429
// 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;
}
Upvotes: 8
Reputation: 51
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];
Upvotes: 5
Reputation:
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
Upvotes: 15