从异步 NSURLConnection 更新 NSmenu [英] Updating an NSmenu from an asynchronous NSURLConnection

查看:60
本文介绍了从异步 NSURLConnection 更新 NSmenu的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个小的系统托盘应用程序,它从 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆