4

API からデータをフェッチし、それに応じてメニューを更新する小さな systray アプリケーションを作成していますが、開いているときにメニューを更新するのに問題があります。

どこから始めればいいのかわからないので、最初から始めましょう。

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

ローダーは、アプリケーションの起動時に 1 回実行され (完全に動作します)、定期的な更新のためにタイマーが設定されます。

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

次に、timed メソッドで、リクエストへの応答を受け取ったら、メソッドを呼び出してメイン スレッド (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 に答える