Timer Trouble

I am playing around with NSTimers. I have a project that will “blink” three buttons on/off according to an associated timer. I have three sliders that I use to adjust the interval for each “blinker.”

In the following handler I am attempting to streamline my process. The sender in this case is a slider. Each of the sliders has a unique tag, 1, 2 or 3. All of the objects are linked correctly and the program works as it should. At the end of the handler is a section of commented-out code showing how I initially set this up. The problem I am having with the new approach is I keep getting the following error:

Running.
Program received signal: “EXC_BAD_ACCESS”.
sharedlibrary apply-load-rules all

I have tracked down the offending line of code to:
currentTimer’s invalidate()

on intervalDidChange_(sender)
		set theTimers to {timer1, timer2, timer3} -- NSTimer properties initialized in applicationWillFinishLaunching
		set theLabels to {label1, label2, label3} -- properties linked to NSTextFields
		
		set currentValue to sender's floatValue() -- get the new value
		set theTag to sender's |tag|() -- a unique number 1, 2 or 3 depending on the sending slider
		
		set currentTimer to item theTag of theTimers
		set currentBlinker to currentTimer's userInfo -- the "blinker" that the timer is associated with
		set currentLabel to item theTag of theLabels
		
		currentLabel's setFloatValue_(currentValue)
		currentTimer's invalidate()
		if currentValue = 0 then -- turn off blinker
			currentBlinker's setState_(0)
		else -- reset timer
			set currentTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(currentValue, me, "updateBlinkerState:", currentBlinker, true)
		end if
		
		(*
		if sender's |tag|() = 1 then -- timer1 / blinker1 / label1
			label1's setFloatValue_(currentValue)
			timer1's invalidate()
			if currentValue ≠ 0 then set timer1 to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(currentValue, me, "updateBlinkerState:", blinker1, true)
			
		else if sender's |tag|() = 2 then -- timer2 / blinker2 / label2
			label2's setFloatValue_(currentValue)
			timer2's invalidate()
			if currentValue ≠ 0 then set timer2 to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(currentValue, me, "updateBlinkerState:", blinker2, true)
			
		else -- timer3 / blinker3 / label3
			label3's setFloatValue_(currentValue)
			timer3's invalidate()
			if currentValue ≠ 0 then set timer3 to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(currentValue, me, "updateBlinkerState:", blinker3, true)
		end if
*)
	end intervalDidChange_

I think I see several problems here – when you reset the timer, you call it currentTimer, but then when you call intervalDidChange again you set theTimers to {timer1,timer2,timer3}, so the timers in the list will never change. Once you invalidate a timer, you’re not allowed to call it again, and since you’re not changing your list, you’re calling an invalidated timer. In any case, the first 2 lines of the intervalDidChange method should be in your applicationWillFinishLaunching method, since you only need to set them once. When you need to reset your timer you should replace the item at index theTag with the new timer (I don’t remember if there is an easy way to do this in an applescript list, but if you use an NSMutableArray instead, then you can use the “replaceObjectAtIndex_withObject_” method).

Another problem is when you turn off the blinker – since that timer has been invalidated, and you don’t get to the else part of the if statement, no new timer is created. Therefore, if you move that slider off of zero, you will be calling an invalidated timer, and the app will crash. You can fix this by creating a new timer (and replacing the old one in the array) after your “currentBlinker’s setState_(0)” statement but just give it a very long time like one million seconds – that’s a little bit of a cheat, but it should work.

I have a sample project that incorporates these changes, which I can post if you want to see it.

Ric

Ric,

Thanks for your reply. I have a question though. You say that I’m not referring to the timers in theTimer. currentTimer is set to one of the timers with the following line:

set currentTimer to item theTag of theTimers

At one time I had log statements logging timer1, timer2, timer3 in my applicationWillFinishLaunching handler and then again right after the line above and both log statements [b]seemed[b/] to show the same timer. Is this [b]not[b/] the case? The answer, of course, is “apparently not” since I seem to be trying to invalidate a non-existant timer. :lol:

Good point about turning off the blinker. I was changing my logic “on the fly” so to speak. Originally I had:

if currentValue = 0 then currentBlinker’s setState_(0)

Previously, currentTimer would always (attempt) to be invalidated. In the course of trying to track down the problem I lost track of the logic here.

Thanks,
Brad

Brad,

The problem isn’t with the “set currentTimer to item theTag of theTimers” statement, that does pick the correct timer out of your list. The problem is with the creation of the new timer with:

set currentTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(currentValue, me, "updateBlinkerState:", currentBlinker, true)

This just assigns a new timer to the variable, currentTimer, it doesn’t change the timers in your list, theTimers, so when you use “set currentTimer to item theTag of theTimers” again, it will get one of the original timers, not your new one.

Ric

Ah, now I see where you are coming from and the only thing I can say is, “D’OH!!!”

What I was/am hoping to be able to do here is streamline the process of invalidating/creating timers so that if I wanted I could have a whole bank of blinkers.

It’s been years since I attempted to learn Obj-C so your offer of posting the sample project would be appreciated. Now that I see what the problem is, setting up a list/mutable array of current timers makes sense.

I have to admit that I had my doubts about working with ASOC at first but I must say I am having a blast learning, screwing up, falling down, picking myself up (with great assistance from the folks here), brushing myself off and learning some more! :smiley:

Thanks again,
Brad

Here is my code. I think I have things hooked up pretty much like you did, with 3 sliders and 3 labels that display the slider’s value and 3 NSButtons for the blinkers – it looks like maybe you were setting the button’s state, I am using the transparent property, so the buttons will completely disappear when they are off.

script BlinkersAppDelegate
	property parent : class "NSObject"
	property label1 : missing value --Outlet for an NSTextField that displays the value of the slider
	property label2 : missing value --Outlet for an NSTextField that displays the value of the slider
	property label3 : missing value --Outlet for an NSTextField that displays the value of the slider
	property blinker1 : missing value --outlet for an NSButton
	property blinker2 : missing value --outlet for an NSButton
	property blinker3 : missing value --outlet for an NSButton
	property theTimers : missing value -- NSMutableArray
	property theLabels : {}
	
	on applicationWillFinishLaunching_(aNotification)
		set timer1 to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(2, me, "updateBlinkerState:", blinker1, true)
		set timer2 to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(10, me, "updateBlinkerState:", blinker2, true)
		set timer3 to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(20, me, "updateBlinkerState:", blinker3, true)
		set theTimers to current application's NSMutableArray's arrayWithObjects_(timer1, timer2, timer3, missing value)
		set blinkers to current application's NSMutableArray's arrayWithObjects_(blinker1, blinker2, blinker3, missing value)
		set theLabels to {label1, label2, label3}
	end applicationWillFinishLaunching_
	
	on intervalDidChange_(sender)
		set currentValue to sender's floatValue() -- get the new value
		set theTag to sender's |tag|() -- a unique number 1, 2 or 3 depending on the sending slider
		set currentTimer to theTimers's objectAtIndex_(theTag - 1) --Use "theTag - 1" because the tags are 1 to 3 while NSArray's indexes start with 0 
		set currentBlinker to currentTimer's userInfo -- the "blinker" that the timer is associated with (this was the problem, because userInfo no longer exists)
		set currentLabel to item theTag of theLabels
		currentLabel's setFloatValue_(currentValue)
		currentTimer's invalidate()
		if currentValue = 0 then -- turn off blinker
			currentBlinker's setTransparent_(1)
			set newTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(1000000, me, "updateBlinkerState:", currentBlinker, true)
			theTimers's replaceObjectAtIndex_withObject_(theTag - 1, newTimer)
		else -- reset timer
			set newTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(currentValue, me, "updateBlinkerState:", currentBlinker, true)
			theTimers's replaceObjectAtIndex_withObject_(theTag - 1, newTimer)
		end if
	end intervalDidChange_
	
	on updateBlinkerState_(sender)
		sender's userInfo()'s setTransparent_(1 - (sender's userInfo()'s isTransparent() as integer))
	end updateBlinkerState_
	
end script

Ric

Ric,

Thanks for the quick reply. I’m going to incorporate your code regarding the arrays into my own code. Just a question about the creation of the arrays – why did you put a “missing value” object at the end of each array. I read the overview of NSMutableArray using AppKiDo but didn’t see anything specific. Is it because my sliders tags are numbered 1 thru 3 and the arrays would be 0 thru 2?

Brad

It has nothing to do with your program. If you look at the method arrayWithObjects: in the docs, you will see that it says you have to end the list with nil (which translates to missing value in ASOC).

Ric

I figured it was something like that. I just didn’t dig deep enough when I looked at the docs.

Thanks,
Brad