4

我正在编写一个小系统托盘应用程序,它从 API 获取数据并相应地更新其菜单,并且在菜单打开时更新菜单时遇到问题。

我什至不知道从哪里开始,所以让我们从头开始。

我有一个自定义PNLinksLoader类,其职责是获取数据并解析它:

- (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];
        }
    }];
}

加载程序在应用程序启动时运行一次(完美运行),然后设置一个计时器用于定期刷新:

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];

现在每当加载器在服务器上加载新数据时,如果菜单关闭,那么一切正常,我打开菜单,新数据就在那里。

但是,如果菜单在刷新期间恰好打开,则什么也不会发生。如果我关闭并重新打开菜单,那么我可以看到新数据。

我想我错过了关于 RunLoop 的一些东西,但我看不到什么(我对它的理解非常稀疏,因为我实际上是在编写这个小应用程序主要是为了学习 Objective-C)。

编辑

performSelector:withObject:这里的问题不是在菜单打开时更新菜单,当我使用而不是performSelectorOnMainThread:withObject:waitUntilDone:modes:在加载程序中时,它实际上有点工作。问题是当我这样做时,在打开菜单时更新菜单时会得到奇怪的结果(关闭菜单时效果很好):

奇怪的行为

在我的菜单中添加一个NSLog呼叫会循环fixes症状,从我在互联网上阅读的内容来看,这可能表明我的线程上有竞争条件(这就是我尝试使用的原因performSelectorOnMainThread,但我似乎无法理解出去。

4

1 回答 1

0

问题是当菜单打开时,当前的运行循环模式不再包含在 NSRunLoopCommonModes 中:它变成了 NSEventTrackingRunLoopMode。

因此,您也必须将计时器添加到此模式的当前运行循环中:

// 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 线程)上的菜单:

[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]];

                       }];

最后,在您的更新方法中,将更改应用到您的菜单数据,并且不要忘记要求菜单更新其布局:

[menu removeAllItems];

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss"];
[menu addItemWithTitle:[formatter stringFromDate:[NSDate date]] action:nil keyEquivalent:@""];

[menu update];
于 2012-08-02T22:39:48.503 回答