Запустить NSRunLoop в программе командной строки Cocoa

#swift #xcode

#objective-c #cocoa

Вопрос:

Возможно ли инициализировать a NSRunLoop без загрузки каких-либо файлов NIB (т. Е. Без вызова NSApplicationMain() )?

Спасибо.

Комментарии:

1. Зачем вам нужен цикл выполнения в приложении CLI?

2. почему вы хотите загружать файлы nib в cli-приложение?

3. бертолами: Я думаю, он имел в виду без .

4. @KennyTM: Одна из причин, по которой можно захотеть это сделать, — использовать такие функции, как CoreLocation (среди множества других), для работы которых требуется цикл выполнения, но по сути не имеет никаких потребностей в графическом интерфейсе.

5. @KennyTM — он может захотеть сделать что-то вроде make a NSURLConnection in main . Без какой-либо специальной обработки он может завершить main работу до того, как программа действительно завершится из-за вызова делегатов.

Ответ №1:

Решение состоит в том, чтобы вызвать NSApplication вручную. Сначала создайте делегата приложения, а затем замените вызов NSApplicationMain() в main.m следующим:

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

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

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

[pool drain];

[delegate release];
  

Делегат будет вызван, когда будет готов, без необходимости ввода

 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
  

Комментарии:

1. Нет никаких причин, по которым обязательно нужно использовать NSApplication (и, следовательно, вводить зависимость от AppKit); все это можно сделать с помощью NSRunLoop. Таким образом, хотя это потенциально полезная информация для кого-то, я думаю, что это неподходящий ответ на этот вопрос. По этой причине голосование отклонено.

2. Я использовал [[NSRunLoop currentRunLoop] run]; в моем приложении без графического интерфейса, но мое приложение переходило в состояние «не отвечает». Для меня этот метод работает нормально.

3. Для ARC удалите [delegate release], [pool drain] и NSAutoreleasePool и поместите @autoreleasepool { } вокруг всего кода.

Ответ №2:

В Swift вы можете добиться этого, добавив следующую строку в конец вашего main.swift :

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

Если вы хотите иметь возможность остановить цикл выполнения, вы должны использовать методы Core Foundation.

 CFRunLoopRun(); // start
  

И вы можете остановить это следующим образом

 CFRunLoopStop(CFRunLoopGetCurrent()); // stop
  

Комментарии:

1. Это здорово … теперь я не знаю, как остановиться. У вас есть какие-либо предложения для меня?

Ответ №3:

Да; вы можете написать свой собственный метод main и запускать NSRunLoop его, не возвращаясь из NSApplicationMain .

Взгляните на эту ссылку; этот парень использует NSRunLoop в своем основном методе, хотя он не загружает файлы NIB, но это должно помочь вам NSRunloops .

Комментарии:

1. Смотрите также: официальная документация NSRunLoop

Ответ №4:

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

Ответ №5:

Следуйте рекомендациям в документации для [запуска NSRunLoop]:

 BOOL shouldKeepRunning = YES;        // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning amp;amp; [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate     distantFuture]]);
  

Комментарии:

1. требуется еще некоторое объяснение — когда я пытаюсь это сделать, функция while() никогда не завершается до даты окончания (и когда для нее установлено значение distantFuture — она просто никогда не существует для проверки shouldKeepRunning). Я пытался удалить все из runloop, но безрезультатно. Сбой CFRunnLoopStop() и…. что ж … требуется дополнительное объяснение. это работает не так, как ожидалось.

2. Привет, устанавливаете ли вы shouldKeepRunning = NO в какой-то момент в том, что было запланировано в вашем runloop? Вы должны запланировать выполнение операций в цикле выполнения, прежде чем также входить в этот цикл while. Удачи!

Ответ №6:

Посмотрите на asynctask.m, который запускает NSRunLoop вручную, чтобы включить использование асинхронных уведомлений «waitForDataInBackgroundAndNotify».

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

Ответ №7:

Вот мое мнение, использующее только DispatchQueue. Большинство инструментов командной строки хотят завершить работу со статусом. Фоновая очередь отправки выполняется одновременно.

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

Ответ №8:

Я только что написал приложение CLI, и мне нужно было запустить runLoop, чтобы класс NSWorkspace работал правильно, а также для получения уведомлений от NSWorkspace. Я сделал это благодаря этому обсуждению, но я не понимаю, почему в большинстве ответов предлагается использовать NSTimer, поэтому я хотел бы поделиться своим решением без него.

Поскольку [Запуск NSRunLoop] «блокирует» поток, а также моя логика CLI блокирует поток, ожидая stdin, в функции main я создаю поток для своей логики приложения CLI, затем я запускаю 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(amp;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;
}
  

Затем в моем потоке я выполняю свою логику CLI и, когда мне нужно получить доступ к API NSWorkspace (для работы которого требуется NSRunLoop) Я использую performBlock: метод в 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);
  }];

  // ...
}
  

Чтобы быть более точным, я решил считывать данные (входные данные) из своего потока и работать с выводом в runLoop, чтобы не иметь параллелизма с ресурсами.