从异步 NSURLConnection 更新 NSmenu [英] Updating an NSmenu from an asynchronous NSURLConnection
问题描述
我正在编写一个小的系统托盘应用程序,它从 API 获取数据并相应地更新其菜单,但在菜单打开时更新菜单时遇到问题.
I am writing a little systray application that fetches data from an API and updates its menu accordingly, and am having trouble with updating the menu while it's open.
我什至不知道从哪里开始,所以让我们从头开始吧.
I don't even know where to start, so let's start with the beginning.
我有一个自定义的 PNLinksLoader
类,它的职责是获取数据并解析它:
I have a custom PNLinksLoader
class whose responsibility is to fetch the data and parse it:
- (void)loadLinks:(id)sender
{
// instance variable used by the NSXMLParserDelegate implementation to store data
links = [NSMutableArray array];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
if (YES == [parser parse]) {
NSArray *modes = [NSArray arrayWithObject:NSRunLoopCommonModes];
[delegate performSelectorOnMainThread:@selector(didEndLoadLinks:) withObject:links waitUntilDone:NO modes:modes];
}
}];
}
加载程序在应用程序启动时运行一次(完美运行),然后设置一个定时器来定期刷新:
The loader is ran one time at the application's startup (works perfectly), and then a timer is setup for periodic refresh:
loader = [[PNLinksLoader alloc] init];
[loader setRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://papyrus.pandanova.com/links"]]];
[loader setDelegate:self];
[loader loadLinks:self];
NSMethodSignature *signature = [loader methodSignatureForSelector:@selector(loadLinks:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:loader];
[invocation setSelector:@selector(loadLinks:)];
NSTimer *timer = [NSTimer timerWithTimeInterval:10 invocation:invocation repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
现在,每当加载程序在服务器上加载新数据时,如果菜单关闭,那么一切正常,我打开菜单,新数据就在那里.
Now whenever the loader loads new data on the server, if the menu is closed, then everything is ok, I open the menu and the new data is there.
但是如果菜单恰好在刷新期间打开,则什么也不会发生.如果我关闭并重新打开菜单,则可以看到新数据.
But if the menu happens to be open during the refresh, then nothing happens. If I close and reopen the menu, then I can see the new data.
我想我遗漏了一些关于 RunLoop 的东西,但我看不到什么(我对它的理解非常稀少,因为我实际上编写这个小应用程序主要是为了学习 Objective-C).
I think I'm missing something about the RunLoop, but I fail to see what (my understanding of it is very sparse, as I'm actually writing this little app mainly to learn Objective-C).
编辑
这里的问题不是在菜单打开时更新菜单,当我使用 performSelector:withObject:
而不是 performSelectorOnMainThread:withObject:waitUntilDone:modes:
在加载器中.问题是当我这样做时,在打开菜单时更新菜单时会得到奇怪的结果(当菜单关闭时效果很好):
The problem here is not to update the menu while it is open, it actually kind of works when I use performSelector:withObject:
instead of performSelectorOnMainThread:withObject:waitUntilDone:modes:
in the loader. The thing is when I do that I get weird results when updating the menu while it's open (works perfectly when the menu is closed):
在我的菜单填充循环中添加一个 NSLog
调用 fixes
症状,从我在互联网上看到的内容来看,这可能表明我有竞争条件在我的线程上(这就是为什么我尝试使用 performSelectorOnMainThread
,但我似乎无法弄清楚.
Adding an NSLog
call in my menu population loop fixes
the symptoms, and from what I read on the internet, it might be a sign that I have a race condition on my threads (that's why I tried using performSelectorOnMainThread
, but I can't seem to figure it out.
推荐答案
问题是当菜单打开时,当前的run loop mode不再包含在NSRunLoopCommonModes中:变成了NSEventTrackingRunLoopMode.
The problem is that when the menu is open, the current run loop mode is not contained in NSRunLoopCommonModes anymore: it becomes NSEventTrackingRunLoopMode.
因此,您也必须将计时器添加到此模式的当前运行循环中:
So you have to add your timer to the current run loop for this mode too:
// use "scheduledTimer..." to have it already scheduled in NSRunLoopCommonModes, it will fire when the menu is closed
menuTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(fireMenuUpdate:) userInfo:nil repeats:YES];
// add the timer to the run loop in NSEventTrackingRunLoopMode to have it fired even when menu is open
[[NSRunLoop currentRunLoop] addTimer:menuTimer forMode:NSEventTrackingRunLoopMode];
然后在你的定时方法中,当你收到对你的请求的响应时,在主线程(UI线程)上调用更新菜单的方法:
Then in your timed method, when you receive the response to your request, call the method to update the menu on the main thread (the UI thread):
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self performSelectorOnMainThread:@selector(updateMenu) withObject:nil waitUntilDone:NO modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}];
最后,在您的更新方法中,将更改应用于菜单数据,不要忘记要求菜单更新其布局:
And finally, in your update method, apply the changes to your menu data and don't forget to ask for the menu to update its layout:
[menu removeAllItems];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss"];
[menu addItemWithTitle:[formatter stringFromDate:[NSDate date]] action:nil keyEquivalent:@""];
[menu update];
这篇关于从异步 NSURLConnection 更新 NSmenu的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!