Is NSTimer auto retained? - objective-c

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

Related

How can I allow a user adjust an NSSlider without pausing the application update loop?

NOTE: Updated below...
I have a cocoa desktop application which consists of a series of controls around a custom NSView. I am using displayLink to drive the updates.
When a user clicks on an NSControl (a slider, a button, a checkbox, a radio button) the application appears to freeze until the mouse is released. I can confirm in fact that the displayLink callback (getFrameForTime) is NOT firing during the time. If I create a timer, that also does not fire, both remain paused until the user releases the mouse, at which point the application resumes updating.
The control is bound, and if I update that value from another thread (for example, via a callback from a MIDI interface) the slider behaves as expected: it moves, the value updates and the application does not pause.
I feel like this should be a fairly obvious fix, but I'm stumped.
Checking "continuous" in IB does as advertised: sends the values continuously, but still exhibits this behavior (preventing the UI update) until the mouse is released.
This seems to be related specifically to mouseDown on NSControl? Why would this block, and do I really need to subclass all my UI elements to change this behavior (seems extreme)
DisplayLink is in its own thread, so why mouseDown on the main thread block it? If this is the case, given the injunction on updating the Cocoa UI from other than the main thread, how do I deal with it?
Any help much appreciated.
Update
Per #Nikolai's comments below, I can confirm that using an NSTimer and adding it to NSEventTrackingRunLoopMode does NOT block. However, I would really like to use CVDisplayLink which (according to the documentation) runs in it's own thread and should not be blocked in this way. Unlike CADisplayLink, I cannot find a way to explicitly assign a runloop to CVDisplayLink (it seems it doesn't work that way), so perhaps the new question should be:
Why does CVDisplayLink block on NSEventTrackingRunLoopMode?
When clicking on an NSControl the runloop mode goes from NSDefaultRunLoopMode to NSEventTrackingRunLoopMode, as long as the mouse is down. That means that only run loop sources (display link) and timers fire that have been added to this mode.
You can add timers to any mode by using -[NSRunLoop addTimer:forMode:]. For a display link the equivalent method is -[CADisplayLink addToRunLoop:forMode:].
To make your animation continue during event tracking you would do something like:
[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSEventTrackingRunLoopMode];
Your test project shows that you are calling a view's display method from within the display link's callback.
When commenting the display message out, the display link is called continuously even while moving the slider.
So what goes wrong is that when the runloop goes into event tracking mode, the call to display on the display link's thread blocks until the mouse is released and the run loop goes back to default mode. You can easily confirm this by putting a log statement before the call and one after it.
Why exactly that happens is not clear to me. What is clear is that it's illegal to call a view's methods from a background thread. You have to trigger the view's display by dispatching a setNeedsDisplay: on the main thread:
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
dispatch_async(dispatch_get_main_queue(), ^{
[(__bridge MyCustomView*)displayLinkContext setNeedsDisplay:YES];
});
return kCVReturnSuccess;
}

NSTimer and NSRunLoop

My app tracks a user with CLLocationManager. In the delegate call didUpdateToLocation I do all the fun stuff of saving their position. However, I needed a way to test if they had stopped. That away I could stop recording locations and consider their trip over. So I have a NSTimer in CCLocationManager that gets added and removed every time didUpdateToLocation is called. That away it will be initiated when the user stops and CLLocationManager stops getting called.
The only way that I could ever get the NSTimer to work is to do:
[[NSRunLoop mainRunLoop] addTimer:userStoppedMovingTimer forMode:NSRunLoopCommonModes];
Then to remove it:
[userStoppedMovingTimer invalidate];
I've never had to add timers like this in the past. Could someone shed some light as to why this is?
From the documentation:
There are three ways to create a timer:
Use the scheduledTimerWithTimeInterval:invocation:repeats: or
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class
method to create the timer and schedule it on the current run loop in
the default mode.
Use the timerWithTimeInterval:invocation:repeats: or
timerWithTimeInterval:target:selector:userInfo:repeats: class method
to create the timer object without scheduling it on a run loop. (After
creating it, you must add the timer to a run loop manually by calling
the addTimer:forMode: method of the corresponding NSRunLoop object.)
Allocate the timer and initialize it using the
initWithFireDate:interval:target:selector:userInfo:repeats: method.
(After creating it, you must add the timer to a run loop manually by
calling the addTimer:forMode: method of the corresponding NSRunLoop
object.)
You were probably using option 1 previously, and now you're using option 2 or 3.

Simple Clock application: use delegation or callback every second?

I'm having some fun with objective c. As a simple program I wanted to write a clock application.
Basically, a UITextField needs to show the current time and update every second.
My initial thought was to use delegation and let UITextField call back a class when the 'Value Changed' event occurs. By 'bootstrapping' an initial value change (e.g. by setting the time at application startup) I thought I could trigger the 'Value Changed' event continuously afterwards (the UITextField would continuously change itself, hence triggering the delegate method). I tried many things, but this never worked. I even tried creating a button that would set UITextField to an arbitrary text value (as opposed to setting UITextField at startup) in the hope that the delegated method would be called, but this did not either. To prove that my code was correct, the time was updated when I'd use other actions like 'Touch Down' for example: I would get the time on every click in the UITextField.
I eventually found out that i could use a callback every second by using [self performSelector ...] and that worked.
Is there a fundamental reason my delegation using the 'Value Changed' action never worked ?
The value changed event only fires in response to a user event-- that is, you setting your textField.text = "something" doesn't fire it, by design.
And it's a good job it doesn't, because by the sounds of it you were trying to get your application into an infinite loop. If the 'value changed' event did actually fire when you set the text in the box, the program would ask the delegate again, which would set the text again, which would ask the delegate again..... you get the picture. This is called an infinite loop, and it has the effect of causing the program to hang, and then crash, since there's no way for the program execution to exit this loop.
Anyway, in order to do what you're saying, you've got two options
you can set up an NSTimer object to call your time update method every second. It's quite easy, check out the documentation.
performSelector:withObject:afterDelay:. It sounds like you might have already got the hang of this one. It's not as neat as using an NSTimer, but it will do the job.

Is there a function for "processing messages", i.e. updating screen, responding to accumulated user input

I have encountered a strange bug in my application, and I am trying to debug it using step execution.
However it seems that things on an iphone often do not happen as synchronously as I would like, for example when I step-over this line
[self.view addSubview:FinndomoEmbeddedMWView.view];
nothing happens in the emulator.
If I just let the program run, the view is added and the screen changes as it should.
So I am guessing, addSubview does not do everything related to adding a view, it just sort of starts the process, and then it is completed later.
I don't know if there are message queues on ios similair to winapi, but there must be something like that, so is there a function for "processing all accumulated messages". I would then like to call this function after my addSubview and actually see the things change in the emulator while I debug, not when the program is running.
And I experience this not only with addSubview, so I want to have a general solution for things like this.
Sat yesterday and found out the answer:
NSDate *dtr = [[NSDate alloc] initWithTimeIntervalSinceNow:0.5];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:dtr];
This is sort of what I wanted. As you can see, this particular code may end up waiting for 0.5 seconds if there are no more events, and if there are events pending, it is only processing the first one and then returns (according to documentation).
But that could be avoided of course, this is just an example, if anyone will want the same thing. In my case there was only 1 important event, and so the provided snippet worked.
The short answer is no.
Cocoa is event-driven.
The core of each app is the event loop. On each pass through the event loop, the app handles events added to the event queue. Updating the screen is one such event.
Thus, changes to screen display don't take place until after your code returns, on the next pass through the event loop.
For debugging purposes where you want to figure out what's happening line-by-line, you need to either use the debugger's facilities, or add NSLog statements to your code.

NSRunLoop freezes with NSTimer and any input

For some reason when I push down (click and hold) on any control, the NSTimer in my App freezes and does not fire until I release the mouse button. It simply does not fire while I have the mouse pressed. This is fine for short periods, but it also freezes if I have a popup menu open, or a combobox dropped down.
I'm not sure if there is something I missed, but it seems like this is incorrect behavior.
I want to be able to click the down arrow of an NSPopUpButtonCell (or even click and hold an NSTableView) without the entire NSTimer freezing (which redraws an NSView).
Any comments / suggestions would be appreciated.
The NSTimer is added to the currentRunLoop with mode NSDefaultRunLoopMode.
While the mouse is down, the run loop is in the NSEventTrackingRunLoopMode. Therefore, any event that's not received onto the EventTracking event queue won't be serviced until the runloop returns to the appropriate mode.
The way around this is to add the timer to the runloop for both modes (default and event tracking).
Instead of adding two or more timers to different runloops or using multithreading (as you won't then be able to update the UI from another thread) simply add the timer to the NSRunLoopCommonModes:
NSTimer *myTimer = [NSTimer timerWithTimeInterval:RefreshInterval target:self selector:#selector(doWork) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSRunLoopCommonModes];
Assuming you're just dealing with one thread in your application, what you described is perfectly expected. By default, NSTimer's are added to the current NSRunLoop which, in your case, is also responsible for dealing with UI interaction. When it gets tied up with handling UI interaction, it can't check your timer and "fire" it.
A solution is to use multi-threading so as to avoid this tie-up. Here's a nice blog post on this very subject: http://blog.narent.com/?p=21
EDIT: See Dave's post for an alternative (and more elegant IMO) solution
Also see:
NSTimer doc
NSRunLoop doc
Threading programming guide