Инструмент Foundation OS X Service, Garbage Collection, MacRuby: почему мой NSRunLoop не зацикливается в acceptInputForMode:beforeDate:?

Я пишу службу OS X с MacRuby. Он увеличивает выделенный текст. В основном это работает, но… ну, вот и все:

#!/usr/local/bin/macruby
# encoding: UTF-8
framework 'Foundation'
framework 'AppKit'

class KCUpcase
  def upcase(pasteboard, userData: s_userdata, error: s_error)
    incoming_string = pasteboard.stringForType "public.utf8-plain-text"
    outgoing_string = incoming_string.upcase
    pasteboard.clearContents
    pasteboard.setString(outgoing_string, forType: "public.utf8-plain-text")
  end
end

NSLog "Starting…"
NSRegisterServicesProvider(KCUpcase.new, "Upcase")
NSLog "Registered…"
NSRunLoop.currentRunLoop\
  .acceptInputForMode(NSDefaultRunLoopMode, 
           beforeDate:NSDate.dateWithTimeIntervalSinceNow(10.0))
NSLog "Done."

Это просто инструмент Foundation, а не часть приложения.

Теперь видите строку NSRunLoop…? Это действительно не работает. Программа немедленно завершает работу. Я полагаю, что цикл выполняется один раз, а затем выходит. Во всяком случае, дело в том, что он определенно не ждет ввода 10 секунд. Итак, вот что я сделал вместо этого:

NSRunLoop.currentRunLoop.runUntilDate NSDate.dateWithTimeIntervalSinceNow(60.0)

И это работает, но, естественно, программа зависает на 60 секунд, и это бесполезно. Поэтому я реализовал все это на Objective C (включая KCUpcase, который не показан). И… это работает. С ручным управлением памятью. Как только я переключаюсь на GC (-fobjc-gc-only), он сразу же закрывается, как и версия MacRuby.

#import <Foundation/Foundation.h>
#import "KCUpcase.h"

int main (int argc, const char * argv[]) {
    NSLog(@"Starting…");

    NSRegisterServicesProvider([[KCUpcase alloc] init], @"KCUpcase");
    NSLog(@"Registered…");

    [[NSRunLoop currentRunLoop]
        acceptInputForMode:NSDefaultRunLoopMode
                beforeDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];
    NSLog(@"Done.");

    return 0;
}

Но, увы, это легко исправить: поскольку это инструмент Foundation (а не NSApplication), кажется, мне придется запускать сборщик мусора вручную, вызвав objc_startCollectorThread. Здесь:

#import <objc/objc-auto.h>
// ...
NSLog(@"Starting…");
objc_startCollectorThread();
NSRegisterServicesProvider([[KCUpcase alloc] init], @"KCUpcase");
// ...

Хорошо, но что тогда с MacRuby? Давайте добавим это в смесь:

#import <MacRuby/MacRuby.h>
// ...
NSLog(@"Starting…");
objc_startCollectorThread(); // This magic stops working once we add MacRuby
[[MacRuby sharedRuntime] evaluateString: @"$stderr.puts 'hi from macruby'"];
NSRegisterServicesProvider([[KCUpcase alloc] init], @"KCUpcase");
// ...

И, опять же, это не ожидание в цикле. И опять же, использование кладжа runUntilDate: вместо acceptInputForMode:beforeDate: работает:

NSLog(@"Starting…");
[[MacRuby sharedRuntime] evaluateString: @"$stderr.puts 'hi from macruby'"];
NSRegisterServicesProvider([[KCUpcase alloc] init], @"KCUpcase");
NSLog(@"Registered…");
// Hmmm…
[[NSRunLoop currentRunLoop]
    runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];
NSLog(@"Done.");
return 0;

Полагаю, я упускаю что-то ужасно очевидное. Пожалуйста, просветите меня.


Кстати, полная версия проекта для MacRuby доступна здесь (download) с заданием Rake, которое создаст и установит его в ~/Library/Services. Затем вам нужно включить его флажок в разделе «Службы» на панели настроек клавиатуры (один раз).

(or git clone git://gist.github.com/537075.git)

Кроме того: интересно, я попытался вызвать NSLog внутри строки MacRuby, и он поднял NoMethodError. Что дает?


person kch    schedule 19.08.2010    source источник
comment
Работает ли версия Objective-C со сборкой мусора, если вы поддерживаете ссылку на объект службы в файле main. то есть вы используете локальную переменную?   -  person JeremyP    schedule 19.08.2010
comment
Я получаю те же результаты, что и выше: завершается при использовании acceptInputForMode…, работает нормально для runUntilDate.   -  person kch    schedule 19.08.2010
comment
Я бы сказал, что по какой-то причине цикл выполнения не думает, что у него есть какие-либо источники ввода.   -  person JeremyP    schedule 19.08.2010
comment
Я бы сказал столько же. Но, похоже, это как-то связано с GC.   -  person kch    schedule 19.08.2010


Ответы (2)


Это немного странно, но вот обходной путь:

framework 'Foundation'
framework 'AppKit'

class KCUpcase
  def upcase(pasteboard, userData: s_userdata, error: s_error)
    incoming_string = pasteboard.stringForType "public.utf8-plain-text"
    outgoing_string = incoming_string.upcase
    pasteboard.clearContents
    pasteboard.setString(outgoing_string, forType: "public.utf8-plain-text")
  end
end

puts "Starting…"
NSRegisterServicesProvider(KCUpcase.new, "Upcase")
puts "Registered…"
later = NSDate.dateWithTimeIntervalSinceNow(5)
NSRunLoop.currentRunLoop.runUntilDate later
puts "Done"

По сути, вам нужно определить метку времени перед отправкой запроса цикла выполнения, иначе основной цикл существует до получения инструкции. Как вы заметили, на самом деле это не ошибка MacRuby, но, надеюсь, это поможет.

person Matt Aimonetti    schedule 22.11.2010
comment
Интересно, что с текущим ночным обновлением MacRuby проблема, похоже, исчезла. - person kch; 03.01.2011
comment
Я думаю, у нас был тикет по этому вопросу, и Лоран действительно его исправил. - person Matt Aimonetti; 21.03.2011

acceptInputForMode:beforeDate: запускает цикл только один раз. Как только любой ввод (кроме таймера) обрабатывается, он завершается. runUntilDate: однако продолжает выполнять цикл до тех пор, пока не будет достигнута дата.

person ughoavgfhw    schedule 03.01.2011