NSmenu update from asynchronous NSURLConnection

I am writing a small systray application that retrieves data from the API and accordingly updates its menu, and I am having problems updating the menu when it opens.

I donโ€™t even know where to start, so let's start from the beginning.

I have a custom class PNLinksLoader , whose task is to PNLinksLoader data and analyze 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 bootloader starts once when the application starts (it works fine), and then the timer is configured for periodic updates:

 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, when the bootloader downloads new data on the server, if the menu is closed, then everything is in order, I open the menu and new data appears.

But if during the update the menu opens, then nothing happens. If I close and open the menu again, I will see new data.

I think I'm missing something about RunLoop, but I donโ€™t see that (my understanding of this is very poor, since I am actually writing this small application to learn Objective-C).

EDIT

The problem is not to refresh the menu while it is open, but it actually works when I use performSelector:withObject: instead of performSelectorOnMainThread:withObject:waitUntilDone:modes: in the bootloader. The fact is that I do what I get strange results when updating the menu when it opens (works fine when the menu is closed):

weird behavior

Adding an NSLog call to my menu, fixes population loop, and from what I read on the Internet, this may be a sign that I have a race condition in my threads (why I tried to use performSelectorOnMainThread , but I cannot figure it out.

+4
source share
1 answer

The problem is that when the menu is open, the current run loop mode is no longer contained in NSRunLoopCommonModes: it becomes NSEventTrackingRunLoopMode.

Therefore, you should also add your timer to the current execution loop for this mode:

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

Then, in your time method, when you get a response to your request, call the method to update the menu in the main thread (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 the data in your menu and do not forget to ask 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]; 
0
source

All Articles