Problems invalidating & re-creating NSTimer(s) - objective-c

I'm having problems starting & stopping NSTimers. The docs say that a timer is stopped by [timer invalidate];
I have a timer object declared as such
.h
NSTimer *incrementTimer;
#property (nonatomic, retain) NSTimer *incrementTimer;
.m
#synthesize incrementTimer;
-(void)dealloc {
[incrementTimer release];
[super dealloc];
}
-The usual.
When it's needed, my method does the following:
-(void)setGenCount {
if(!condition1 && condition2) {
incrementTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:#selector(incrementBatteryVoltage:)
userInfo: nil
repeats: YES];
}
}
Everything above works fine. However, once that timer does it's job, I want it to invalidate itself. I invalidate the timer because there is an equal decrement method that could be called and would fight against the incrementTimer if it was still active. (Previously, I noticed that my two timers, if active, were acting on the same ivar by increasing & decreasing the value (a sort of fight)... without crashing) The selector called works as follows:
-(void)incrementBatteryVoltage:(NSTimer *)timer {
if(battVoltage < 24.0) {
generatorDisplay.battVoltage += 0.1;
}
if(battery1Voltage == 24.0) {
[timer invalidate];
}
}
I have an equal method that Decrements the battery count. (previously mentioned)
Due to my program design: the interface simulates a voltage display. When the "machine" is turned off, I want all the timers invalidated, regardless of what any voltage value is. I'm doing this by checking to see if the timer is valid.
-(void)deEnergizeDisplays {
if([decrementTimer isValid]) {
[decrementTimer invalidate];
decrementTimer = nil;
}
if([incrementTimer isValid]) {
[incrementTimer invalidate];
incrementTimer = nil;
}
I'm getting numerous "BAD_ACCESS" crashes. The erroneous line call is always pointing toward my [timer isValid] call. It seems that if the timer is invalidated... the pointer
doesn't exist either. I know that the [timer invalidate] message disables the timer and then it is removed from the run loop and then it is released. And my understanding is: it is an autoreleased object per it's naming covention.
My thought are: If I'm sending a retain message, shouldn't the reference still exist? I've tried several combinations, taking away:
timer = nil;
or even instead of:
if([timer isValid])
I tried :
if([timer != nil])
and:
if(timer)
I always get the same crash. Thanks for any help on starting & stopping NSTimers.

UPDATE: See Darren's answer. The problem is that you are not using your property accessor when setting the timers. Instead of:
incrementTimer = [NSTimer ...
You should have:
self.incrementTimer = [NSTimer ...
The self.propertyName = ... syntax will call your accessor method, and thereby automatically retain the object that you send to it (since your property is set up as retain). Simply calling propertyName = ... does not use the property accessor. You are simply changing the value of your ivar directly.
UPDATE #2: After an enlightening conversation with Peter Hosey (see comments), I have removed my earlier suggestion to "never retain or release" your timer object. I have also completely re-written my earlier code because I think the following is a better approach:
Controller.h:
NSTimer *voltageTimer;
float targetBatteryVoltage;
...
#property (nonatomic, retain) NSTimer *voltageTimer;
Controller.m:
#implementation Controller
#synthesize voltageTimer;
- (void)stopVoltageTimer {
[voltageTimer invalidate];
self.voltageTimer = nil;
}
- (void)setTargetBatteryVoltage:(float)target {
[voltageTimer invalidate];
targetBatteryVoltage = target;
self.voltageTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector: #selector(updateBatteryVoltage:)
userInfo: nil
repeats: YES];
}
- (void)updateBatteryVoltage:(NSTimer *)timer {
const float increment = 0.1;
if (abs(battVoltage - targetBatteryVoltage) < increment) {
[timer invalidate];
}
else if (battVoltage < targetBatteryVoltage) {
generatorDisplay.battVoltage += increment;
}
else if (battVoltage > targetBatteryVoltage) {
generatorDisplay.battVoltage -= increment;
}
}
Now, you can simply set a target battery voltage, and the timer magic will happen behind the scenes:
[self setTargetBatteryVoltage:24.0];
Your power-off method would look as follows:
- (void)deEnergizeDisplays {
[self stopVoltageTimer];
}

You need to retain the value assigned to incrementTimer in setGenCount. You can do this automatically by using your synthesized property, which is accessed via self.:
self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval: ...

Related

Display NSArray at specific intervals

I have an NSArray that I would like to display each object at a specific time interval, for example:
NSArray *array = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
and output it:
for (object in array) {
NSLog(#"%#", object);
// wait 3 seconds then show next object
}
so it should do this:
foo
... wait three seconds
bar
... wait three seconds
baz
... wait three seconds
repeat
how can i do this?
The simplest approach would be to use Blocks...
You can explore how to do this with a Blocks using third party categories such as :
https://github.com/zwaldowski/BlocksKit
As for a solution without blocks:
#property (nonatomic, strong) NSArray *myArray;
#property (nonatomic) NSInteger iteration;
- (void)awakeFromNib //Or equivalent method called when you want to start scheduling..
{
self.myArray = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
self.iteration = 0;
[NSTimer scheduledTimerWithTimeInterval:3
target:self
selector:#selector(fireEvent)
userInfo:nil
repeats:YES]; //Will Fire every 3 seconds...
}
-(void)fireEvent
{
NSLog(#"%#", [self.myArray objectAtIndex:self.iteration]);
self.iteration++;
if (self.iteration>=self.myArray.count) self.iteration=0;
}
If you with to invalidate the timer after all elements from the array are shown. Do the following :
-(void)awakeFromNib //Or equivalent method called when you want to start scheduling..
{
self.myArray = [NSArray arrayWithObjects:#"foo",#"bar",#"baz",nil];
self.iteration = 0;
[NSTimer scheduledTimerWithTimeInterval:3
target:self
selector:#selector(fireEvent:)
userInfo:nil
repeats:YES]; //Will Fire every 3 seconds...
}
-(void)fireEvent:(NSTimer*)timer
{
NSLog(#">>>> %#", [self.myArray objectAtIndex:self.iteration]);
self.iteration++;
if (self.iteration>=self.myArray.count) {
self.iteration=0;
[timer invalidate];
}
}
NOTE: The latter code only adds a ':' to the #selector(fireEvent:). And reads the NSTimer reference in the fireEvent as a parameter.
Depending on the surrounding context, you might be able to do what you want by letting an NSRunLoop run for 3 seconds. Alternatively, and perhaps a bit safer and saner, you could use an NSTimer to run an action every 3 seconds.
I'd originally suggested breaking up the loop, logging the first element, then perform the same action on the rest of the array after a 3-second delay, using either something like performSelector:withObject:afterDelay: or dispatch_after, but as danielbeard pointed out, NSTimer provides a much more straight-forward way to cancel the action (cancellation is possible with the other methods, but requires planning and manual work).
Edit: If I were going to do this with an NSTimer, I'd probably use this NSTimer+Blocks category and do something like (untested code):
__block NSUInteger index = 0;
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:3.0 block:^{
NSLog(#"%#", array[index]);
index++;
if (index >= [array count]) {
[timer invalidate];
}
} repeats:YES];

passing a block in #selector()

How do I pass a block, and what would it look like, in the method incrementCount:completion to get the property self.count returned after its increment in the CounterClass? I'm not sure if the way I defined the block parameter (void(^)(void))callback; in the method is correct i.e. should it also have a return value?
ViewController
[NSTimer scheduledTimerWithTimeInterval:3.0
target:self.counterClass
selector:#selector(incrementCount:completion:)
userInfo:nil
repeats:YES];
CounterClass
-(void)incrementCount:(NSTimer *)timer completion:(void(^)(void))callback;
{
self.count += 1;
}
NSTimer expects to call a method which takes zero or one parameters, if there is a parameter it should be the timer instance itself.
So, you can't have a method with 2 parameters where one is a block.
Instead, remove the second parameter and simply call another method or block in the method implementation. The block could be stored as an #property of the class.
You can used dispatch_after.
ViewController:
[self.counterClass incrementCountWithCompletion:^{
// Your block code
NSLog(#"block code");
}];
CounterClass:
-(void)incrementCountWithCompletion:(void(^)(void))block;
{
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_main_queue(); // Choose whatever queue is approapriate to you
//Beware of retain cycles and use weak self pattern appropriately
dispatch_after(delay, queue, ^{
self.count += 1;
block();
[self incrementCountWithCompletion:block];
});
}
You can add your block to the userInfo:
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self.counterClass selector:#selector(incrementCount:) userInfo:#{#"completion" : [yourBlock copy]} repeats:YES];
CounterClass
- (void)incrementCount:(NSTimer *)timer {
self.count += 1;
void (^completion)(void) = timer.userInfo[#"completion"];
}
For more on storing a block in a dictionary: blocks in nsdictionary?

Using willChangeValueForKey/didChangeValueForKey with OSSpinLockLock

I want to issue a notification when all 4 different threads have finished their work. I'm keeping count of the total threads and have a listener that does some work when the threads have finished.
Is the following a safe way to do this?
// ivars:
NSMutableArray *list;
OSSpinLock lock;
#define MAX_ALLOWED 4
- (void)someThreadedWork
{
// Iterate thru 4 different items using gcd and update
for (int x = 0; x < MAX_ALLOWED; ++x)
{
dispatch_async(some_queue, ^{
// Do some work.. once done,
[self updateCount:ix];
});
}
}
- (void)updateCount:(NSInteger)newCount
OSSpinLockLock(&lock);
{
[list addObject:[NSNumber numberWithInt:newCount]];
if ([list count] == MAX_ALLOWED)
{
_allValuesUpdatedAt = [NSDate date];
}
}
OSSpinLockUnlock(&lock);
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object
change:(NSDictionary*)change context:(void*)context
{
// When I get the 'allItemsUpdatedAt' event, I will perform some other work
}
- (id)init {
if (self = [super init])
{
// there is a corresponding removeObserver in the dealloc
list = [[NSMutableArray alloc] init];
[anInstance addObserver:self
forKeyPath:#"allItemsUpdatedAt"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
HachiEthan's comment about dispatch_async is exactly correct. If you use dispatch_group_async instead, then you don't need any locking at all, nor do you have to keep track of the threads that are currently working. It'll do all of this for you.
See "Waiting on Groups of Queued Tasks" in the Concurrency Programming Guide for a faster, simpler, more robust, and less energy intensive approach to this problem. See also "Migrating Away from Threads" in the same document to learn how to convert thread-based systems to queue-based systems.
Heres a few things:
Red Flag: You change the value of _allValuesUpdatedAt inside the lock, but are potentially "reading" its value elsewhere... (e.g. whomever is observing the property allValuesUpdatedAt) so unless your allValuesUpdatedAt property is also protected by that same &lock instance, you've got a problem there. (Edit: also, your above code is not retaining the NSDate, so it will be autoreleased on you at some point.)
If you use dispatch_async and pass it a serial queue then you don't need that spin-lock for updating the values in your list, provided that the updateCount: method is only accessed by code running from that serial queue. (Note: You'd still need to protect access to allValuesUpdatedAt)
If updateCount: does need to be accessed from other code, say, the main thread, then yes you will need some sort of lock there, like you have.
EDIT: keeping most of what you have, but addressing the red-flag
//header:
#property (atomic, retain) NSDate *allValuesUpdatedAt;
//impl:
- (void)updateCount:(NSInteger)newCount
OSSpinLockLock(&lock);
{
[list addObject:[NSNumber numberWithInt:newCount]];
if ([list count] == MAX_ALLOWED)
{
//use atomic property for read/write thread-safety:
self.allValuesUpdatedAt = [NSDate date];
}
}
OSSpinLockUnlock(&lock);
}

Objective-C invalidate timer in another method

I have a method in which i have declared a timer;
- (void)startTimer:(id)sender {
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval: 0.8
target: self
selector: #selector(toggleButtonImage:)
userInfo: nil
repeats: YES];
}
What i want to do is, in another method i want to invalidate the timer if it is running, here's the what i have so far but i get the error 'timer is undeclared'
- (void)stopTimer:(id)sender {
if ( [timer isValid]) {
[timer invalidate], timer=nil;
}
}
Could anyone help me?
If both methods are on the same controller, then simply make the timer an instance variable. If they are not on the same object, you should rethink your design as two classes are trying to manage the same facility.

Xcode - Something may not respond to

I'm getting some warnings that somethings may not respond to a function.
#import "Lane.h"
#import "TrafficController.h"
#implementation Lane
#synthesize controller;
-(void)awakeFromNib
{
[controller registerLane:self]; -- 'TrafficController' may not respond to '-registerlane'
srandom(time(NULL));
[self start];
}
-(void)start
{
long newStartTime = random() % 200;
carStartTimer = [NSTimer scheduledTimerWithTimeInterval:newStartTime / 1000.0 target:self selector:#selector(startTimerFired:) userInfo:nil repeats:YES];
[carStartTimer retain];
}
-(void)startTimerFired: (NSTimer*)timer
{
//pick a random number of milliseconds to fire again at
long newStartTime = random() % 1500 + 500;
[timer setFireDate:[NSDate datewWithTimeIntervalSinceNow:newStartTime / 1000.0]];
[controller startCarFromLane:self]; - 'TrafficController' may not respond to '-startCarFromLane'
NSLog(#"Starting new car");
}
-(void)stop
{
[carStartTimer invalidate];
[carStartTimer release];
carStartTimer = nil;
}
#end
Does it mean that those functions arent declared in this or other header file? Or does it mean something else?
In your TrafficController.h, do you have a line like:
-(void) registerLane: (type) variablename;
If you don't, you should have
Do what #Dave says and let the complier know about your methods in your header file.
You can also quiet that message by manipulating the order of the methods in your implementation file. If the compiler has already seen the method (in this case -(void)registerLane:), then it won't bellyache when you call it later, even if it's not in your .h file.
That way you can have internal methods that aren't advertised as available in your .h but you can call them internally without compiler warnings. BUT, in most cases this is a mistake and you probably just forget to put it in the .h file.