从异步 NSURLConnection 更新 NSmenu

2023-12-19

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

EDIT

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

添加一个NSLog调用我的菜单人口循环fixes这些症状,从我在互联网上读到的内容来看,这可能表明我的线程上存在竞争条件(这就是为什么我尝试使用performSelectorOnMainThread,但我似乎无法弄清楚。


问题是,当菜单打开时,当前的运行循环模式不再包含在 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];
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从异步 NSURLConnection 更新 NSmenu 的相关文章

随机推荐