Calling a method either the second time a condition occurs OR after a timeout, whichever comes first - objective-c

After plenty of coding in the past days, I'm finally (um) pretty much stuck.
My application listens for an external NSNotification sent by iTunes. The notification is sent out whenever the current playing status changes, in this case most interestingly when the current stream title changes. When you connect to a new radio station, two notifications are usually sent - one as soon as iTunes connects, with the station name as the title, and one soon thereafter (a second or so) with the actual artist and title of the current song. I'm only interested in the artist/title combo, or the second notification. Or, if there's still only one notification sent after a two second pause, use the first one. (Since there's no way of knowing whether there will be one or two beforehand, the timeout is the only way I can think of.)
In fewer words, I want to call a method only the second time a condition occurs, OR after a two-second timeout if only one notification is sent. It should reset back to do the whole deal again after the two seconds have passed.
Any ideas?

This isn't too complicated. You just need to hang on to the first notification until either the timer fires or the second notification comes in. The comments I put in the code should explain the procedure.
- (void)awakeFromNib {
// Register for the notification you're interested in
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:#selector(iTunesNoteCallback:)
name:NSTheiTunesNotificationImInterestedIn
object:nil]; // #"iTunes"?
}
- (void)iTunesNoteCallback:(NSNotification *)note {
// Check whether there's been a notification already
if( !gotFirstNote ){
// If so, hang on to it,
gotFirstNote = YES;
self.currNote = note; // With currNote declared as a retained property
// and start a timer.
noteTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(actOniTunesInfo:)
userInfo:nil repeats:NO];
}
else {
// However, if we got a notification already,
// hang on to the new one,
self.currNote = note;
// stop the timer,
[noteTimer invalidate];
// and call the same method the timer would have
[self actOniTunesInfo:nil];
}
}
- (void)actOniTunesInfo:(NSTimer *)timer {
// Reset the flag
gotFirstNote = NO;
// Use currNote; doesn't matter which one it is,
// it's the best info we've gotten
}
If there are two types of notifications, it's even simpler. You no longer need the flag, you just register two callbacks, and as soon as the second one is called, you can invalidate the timer, release the first notification, and use the info you've just gotten.

You need to create a data model that can store and model the notfications as well as their order and timing.
First you would need a data object to store each notification and data about the notification.
- the notification object
- the time stamp when the notification arrived
- type of notification
Then you would need a container-object that could hold the data-objects in an array as well as start and catch timers. So, when a notification arrives, it is classified, stored in a data object which is then pushed onto the array. If it is the first type of notification the container object starts a two second timer. If another notification of the second type arrives, the container-object kills the timer but if the timer fires, then it returns the data-object of the last first type notification that is more than two seconds old.
Once you've triggered an action, empty the container-object and start over.

Related

Using NSMutableArray as a queue not working

Strange issue while using NSMutableArray as a queue.
Given a singleton object SO, a ViewController object VC, and a NSMutableArray MA.
MA is being used as a queue for an ID that is received by SO, and passed to VC via putting the ID in MA and sending a notification that VC handles. There is a problem with MA losing what it holds.
Steps relating to issue:
Seque is initiated to load a view.
SO requests an ID from a server.
VC’s viewDidLoad completes
SO block function receives an ID and puts it in MA, and sends a notification to VC.
VC receives notification and retrieves ID from MA
The first time the view loads there is no problem. However if I go out of the view and back into it repeating the steps above the size of MA in step 4 is 1, and the size of MA in step 5 is 0. I.e. MA becomes empty!
I’m perplexed as to how MA mysteriously becomes empty between the time the notification is sent (step 4), and when the notification handler is called (step 5). I’ve checked that nothing outside of the VC notification handler is clearing the array.
A possible clue is that there is a method called (message sent in obj C parlance) from viewDidLoad that if commented out the issue described doesn’t happen. Also if I put the following line after the method in viewDidLoad (instead of commenting it out) then the issue stops occurring:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.5]];
I’ve been scratching my head on this one. My latest thought is that there is something about NSMutableArray that I don’t understand. I thought MA was storing strong references to IDs placed in it, but if it weren’t then maybe there’d be some strangeness.
Anyone have ideas?
The problem was that NSNotificationCenter addObserver: was called every time VC's viewDidLoad completed(step 3). This was causing the notification handler to be called as many times as it was added, even if the notification itself was posted only one time.
The following is related: How to stop the Observer in NSNotification to called twice?

how to receive NSWorkspace and accessibility notifications on another thread

I am trying to do window management, but I need the code running on a separate thread.
The first thing I need to do is subscribe to app notifications like this:
NSNotificationCenter *nc = [[NSWorkspace sharedWorkspace] notificationCenter];
NSString *not = NSWorkspaceDidLaunchApplicationNotification;
[nc addObserver:self selector:#selector(appLaunched:) name:not object:nil];
But if I simply call addObserver on another thread, will the notifications be delivered there instead?
Apple has this reference, but it seems overcomplicated:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Notifications/Articles/Threading.html
If the answer to the first question is no, then why couldn't I just forward the message like this?
NSThread *other;
- (void)appLaunched:(NSNotification*)not {
if([NSThread currentThread] != otherThread)
[self performSelector:#selector(appLaunched:) onThread:other withObject:not waitUntilDone:NO];
else
// do respond to notification
}
The second thing I need to do is add an AXObserver to a runloop on the other thread.
If I call CFRunLoopGetCurrent() from another thread, will a run loop automatically be created like calling [NSRunLoop currentRunLoop] or do a I have to create one?
Observers which are registered using -addObserver:selector:name:object: receive the notification on the thread where it's posted, not where they registered. There's also -addObserverForName:object:queue:usingBlock:, which causes the notification to be received on the specified queue, but that doesn't let you make it arrive on a specified background thread. (Only the main queue is tied to a thread.)
You can shunt a notification to another thread in the manner you suggest. However, the original receiving thread has to be idling to receive the notification in the first place. Or, rather, it has to be idling in order to allow NSWorkspace to detect the condition which causes it to post the notification.
All threads create a runloop for themselves as soon as it's requested. It's basically impossible to observe a thread not having a runloop, so you might as well just act as though the runloop is created when the thread is created.
All of that said, your original goal – "I am trying to do window management, but I need the code running on a separate thread" – is problematic. Many GUI manipulations are not legal from background threads. Also, why do you "need" to do it from a background thread? And if your main thread is not free, you're not going to receive the workspace notifications in the first place.

iOS auto logout of application

I am wanting to create a timer or something of sorts to auto logout the user after x minutes of inactivity. I would like to do it the same way the Bank of America application does it. The way the BofA app does it is even when the application is put into the background it still keeps track of the time. When the time limit is reached a notification will popup stating you are being logged out.
How can this be done without the timer being suspended when the application goes into the background?
I think maybe the simplest thing you can do is register your AppDelegate with the NSNotificationCenter to listen for all events from all (or maybe specific) senders.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(resetTimer) name:nil object:nil];
You need to take care with passing nil to the name and object parameters as you will get a ton of notifications (even some not originating from the application, i.e., memory warnings). If you know of or have the list of specific event names and/or objects I would observe on those instead.
In your resetTimer method, you will simply invalidate the previous timer and create a new one that will call some logout method AND set an iVar to the current date/time (i.e., timerStart = [NSDate now];)
The above steps will take care of your app while it is in the foreground.
When the app is backgrounded, the timers will quit working. However, when the app returns to the foreground, you can calculate the delta between [NSDate now] and your timerStart iVar. If the delta is greater than some interval, you invoke your logout method. If not, you can just call resetTimer to start your timers again.
EDIT
If you want the backgrounded app to alert that the user is about to be logged out, you can use a UILocalNotification. You can schedule one to alert when the application enters the background. When the application enters the foreground, you can cancel that notification (and perform the steps I mention above).

how to update UI controls in cocoa application from background thread

following is .m code:
#import "ThreadLabAppDelegate.h"
#interface ThreadLabAppDelegate()
- (void)processStart;
- (void)processCompleted;
#end
#implementation ThreadLabAppDelegate
#synthesize isProcessStarted;
- (void)awakeFromNib {
//Set levelindicator's maximum value
[levelIndicator setMaxValue:1000];
}
- (void)dealloc {
//Never called while debugging ????
[super dealloc];
}
- (IBAction)startProcess:(id)sender {
//Set process flag to true
self.isProcessStarted=YES;
//Start Animation
[spinIndicator startAnimation:nil];
//perform selector in background thread
[self performSelectorInBackground:#selector(processStart) withObject:nil];
}
- (IBAction)stopProcess:(id)sender {
//Stop Animation
[spinIndicator stopAnimation:nil];
//set process flag to false
self.isProcessStarted=NO;
}
- (void)processStart {
int counter = 0;
while (counter != 1000) {
NSLog(#"Counter : %d",counter);
//Sleep background thread to reduce CPU usage
[NSThread sleepForTimeInterval:0.01];
//set the level indicator value to showing progress
[levelIndicator setIntValue:counter];
//increment counter
counter++;
}
//Notify main thread for process completed
[self performSelectorOnMainThread:#selector(processCompleted) withObject:nil waitUntilDone:NO];
}
- (void)processCompleted {
//Stop Animation
[spinIndicator stopAnimation:nil];
//set process flag to false
self.isProcessStarted=NO;
}
#end
I need to clear following things as per the above code.
How to interrupt/cancel processStart while loop from UI control?
I also need to show the counter value in main UI, which i suppose to do with performSelectorOnMainThread and passing argument. Just want to know, is there anyother way to do that?
When my app started it is showing 1 thread in Activity Monitor, but when i started the processStart() in background thread its creating two new thread,which makes the total 3 thread until or unless loop get finished.After completing the loop i can see 2 threads.
So, my understanding is that, 2 thread created when i called performSelectorInBackground, but what about the thrid thread, from where it got created?
What if thread counts get increases on every call of selector.How to control that or my implementation is bad for such kind of requirements?
Thanks
how to update UI controls in cocoa application from background thread
Simple: Don't.
How to interrupt/cancel processStart while loop from UI control?
Outside of processStart, set a flag variable. Inside of processStart, check that flag and exit the loop if it is set.
Don't try to “kill” a thread from another thread. It's always a bad idea. Tell the thread it's time to stop by setting the flag, and have the thread check that flag and stop at an appropriate time.
I also need to show the counter value in main UI, which i suppose to do with performSelectorOnMainThread and passing argument. Just want to know, is there anyother way to do that?
Yes.
When my app started it is showing 1 thread in Activity Monitor, but when i started the processStart() in background thread its creating two new thread,which makes the total 3 thread until or unless loop get finished.After completing the loop i can see 2 threads. So, my understanding is that, 2 thread created when i called performSelectorInBackground, but what about the thrid thread, from where it got created?
Profile your app using Instruments or Shark and look. It's probably the heartbeat thread for the progress indicator.
What if thread counts get increases on every call of selector.How to control that or my implementation is bad for such kind of requirements?
Every performSelectorInBackground:withObject: message starts a thread. If your thread count isn't going down, it's because your thread method didn't exit. If your thread count is too high, it's (probably) because you started too many threads.
There is a much better way to do this.
First, the general rule in Cocoa is never sleep. Think of this as special ultra-caffeinated Cocoa. For anything you might sleep for in another framework, there is almost always a better, usually easier, way in Cocoa.
With that in mind, look at processStart. All it does is do something every centisecond. How best to do that?
Cocoa has a class for this specific purpose: NSTimer. Create a timer that sends yourself a message at the desired interval, and respond to that message by updating the progress bar—that is, your timer callback method should essentially just be the loop body from processStart, without the loop.
By the way, 100 updates per second is overkill. First off, the user does not care that you have made 1/5th of a pixel's worth of progress since the last time you updated the bar. Second, the screen only updates about 60 times per second anyway, so updating anything visible faster than that is pointless.
- (void)dealloc {
//Never called while debugging ????
[super dealloc];
}
Assuming you put your app delegate in the MainMenu nib, the application object owns it because of that—but it doesn't know that, because it only knows about the app delegate as its delegate, which is a non-owning relationship. (And even if it were an owning relationship, that would just be two ownerships, of which the app would release one, which wouldn't help.)
However, the lifetime of the app delegate doesn't really matter. Its purpose as the delegate of the application means that it needs to last about as long as the application does, but when the application goes away, the process is exiting, which means the delegate will be deallocated as well, as part of the reclamation of the process's memory space. That's why dealloc isn't called—the whole process space goes away at once, instead of objects being deallocated one at a time.
So, in principle, yeah, the app delegate not getting explicitly cleaned up is kind of dodgy. In practice, don't put any temporary-files clean-up in its dealloc (use applicationWillTerminate: instead) and you'll be fine.
I typically work around the problem by putting all my real work in one or more other objects which the app delegate owns. The app delegate creates these other controllers in applicationWillFinishLaunching: and releases them in applicationWillTerminate:, so those objects do get dealloc messages. Problem solved.

Is NSTimer auto retained?

I have a -(void)save method that is called when a user clicks a navigation bar button. In that method is the following NSTimer:
[NSTimer scheduledTimerWithTimeInterval:.25f target:self selector:#selector(flashBackgroundRed) userInfo: nil repeats: flashRepeat];
The timer repeats 4 times since the flashBackgroundRed keeps a count and sets flashRepeat to NO after 4 hits. All works well the first time a user clicks to save. But if the user keeps hitting save with incorrect data, I keep them on the current view. For some reason, flashes get increasing faster. As if each one is 25% of the previous one's interval. I'm not retaining this timer. It only lives in the save method.
If the user clicks the cancel button, viewB (one with NSTimer) is popped and viewA displays. Going from viewA back to viewB seems to reset the timer invterval. But the increasingly fast flashes cycle starts again. Any ideas?
NSTimer is retained by the run loop as long as it is scheduled.
It sounds like the problem is that you keep creating equivalent repeating timers at slightly different times, so they visually mesh together into one effect with a different frequency than you want. Try storing a reference to the timer and invalidating it when you're going to create a new one.
It sounds like you are scheduling more timers than you intend to.
Once scheduled, timers are retained until invalidated. Repeating timers must be manually invalidated.
The repeats argument specifies if the timer will repeat. You can't specify how many times a timer should repeat, only whether or not it will.
Also, the method signature for your selector is wrong. It should look like
-(void)timerFireMethod:(NSTimer*)theTimer
In your timer callback you can determine if you want the timer to continue repeating or not; if not, call [timer invalidate].