基础工具 OS X 服务、垃圾收集、MacRuby:为什么我的 NSRunLoop 不会在 AcceptInputForMode:beforeDate: 中循环?

发布于 2024-09-14 16:20:51 字数 3201 浏览 8 评论 0原文

我正在使用 MacRuby 编写 OS X 服务。它将选定的文本转大写。它基本上可以工作,但是......好吧,这就是全部:

#!/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."

它只是一个基础工具,而不是应用程序的一部分。

现在,看到 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 来手动启动 GC。这里:

#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: kludge 而不是 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 版本可以在 此处 (下载),带有一个 Rake 任务,该任务将在 ~/图书馆/服务。然后,您需要在键盘首选项窗格中的服务中启用其复选框(一次)。

(或git clone git://gist.github.com/537075.git

旁白:有趣的是,我尝试在MacRuby内部调用NSLog字符串,并引发了 NoMethodError。什么给?

I'm writing an OS X Service with MacRuby. It upcases the selected text. It mostly works, but… well, here's all of it:

#!/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."

It's just a Foundation tool, not part of an Application.

Now, see the NSRunLoop… line? That doesn't really work. The program exits imediately. I suppose the loop runs once and then exits. Anyhoo, the fact is that it's definititely not waiting 10s for input. So, here's what I did instead:

NSRunLoop.currentRunLoop.runUntilDate NSDate.dateWithTimeIntervalSinceNow(60.0)

And that works, but naturally the program sticks around for 60s, and it's a kludge. So I implemented the whole thing in Objective C (Including KCUpcase, which is not shown). And… it works. With manual memory management. Once I switch to GC (-fobjc-gc-only), it exits imediately same as the MacRuby version.

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

But, alas, the fix is easy: because this is a Foundation tool (not an NSApplication), it seems I have to start GC manually by calling objc_startCollectorThread. Here:

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

Ok, but what's up with MacRuby then? Let's throw it into the mix:

#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");
// ...

And, again, it's not waiting in the loop. And, again, ussing the runUntilDate: kludge instead of acceptInputForMode:beforeDate: works:

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;

So, I suppose I'm missing something terribly obvious. Please enlighten me.


And by the way, the full MacRuby version of the project is available here (download) with a Rake task that'll build and install it in ~/Library/Services. Then you need to enable its checkbox in Services in the Keyboard Preference Pane (once).

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

Aside: Interestingly, I tried calling NSLog inside the MacRuby string, and it raised NoMethodError. What gives?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

酒废 2024-09-21 16:20:51

这有点奇怪,但这里有一个解决方法:

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 错误,但仍然希望这会有所帮助。

It's a bit weird but here is a workaround:

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"

Basically, you need to define the timestamp before you dispatch the runloop request otherwise the main loop exists before getting the instruction. As you spotted, this is not really a MacRuby bug but still, hopefully that helps.

梦里兽 2024-09-21 16:20:51

AcceptInputForMode:beforeDate: 仅运行循环一次。一旦处理任何输入(定时器除外),它就会退出。 runUntilDate:但是继续运行循环,直到到达日期。

acceptInputForMode:beforeDate: only runs the loop once. As soon as any input (other than a timer) is processed, it exits. runUntilDate: however continues running the loop until the date is reached.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文