基础工具 OS X 服务、垃圾收集、MacRuby:为什么我的 NSRunLoop 不会在 AcceptInputForMode:beforeDate: 中循环?
我正在使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这有点奇怪,但这里有一个解决方法:
基本上,您需要在分派运行循环请求之前定义时间戳,否则主循环在获取指令之前就存在。正如您所发现的,这并不是真正的 MacRuby 错误,但仍然希望这会有所帮助。
It's a bit weird but here is a workaround:
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.
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.