passing a block in #selector() - objective-c

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?

Related

Get a local variable from a function and implement the variable in another function which can be changed dynamically

my app has a function, it gets a value from a NSTextField and then declare the variable, like this:
- (IBAction)startTimer
//all the other code
int totalTime = secs + hoursInSeconds + minutesInSeconds
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerHandler) userInfo:nil repeats:YES];
then, i want to use the local variable totalTime in another function which processes the NSTimer.
- (void)timerHandler
//all other code
totalTime = totalTime - 1;
//invalidate timer when it reaches 0
if (totalTime == 0.0) {
[timer invalidate];
however, as the variable totalTime is a local variable, i cannot use the value, and i cannot move the code over as NSTimer calls it every 1 sec and as the user may change the variable (and thus redeclaring it).
so, is there any way i can get a local variable from a function and implement the variable in another function which can be changed dynamically? or can i implement a NSTimer countdown by just using one function
You could wrap the value in the timer's userInfo:
NSNumber *totalTimeNumber = [NSNumber numberWithInt:totalTime];
timer = [NSTimer scheduledTimerWithTimeInterval:... target:... selector:... userInfo:totalTimeNumber repeats:...];
Or just make it an instance variable.
Well, here's a fun one that works with local variables, instead of instance variables but only on Mac OS 10.6/iOS 4 and above:
-(IBAction)startTimer:(id)sender
{
// ensure, that the variables we'll capture in the block are mutable
__block int totalTime = ...
__block NSTimer *timer;
void (^timerBlock)() = ^{
if (--totalTime <= 0) { // this comparison is much less fragile...
[timer invalidate];
}
};
// If you'd call timerBlock() at this point you'll crash because timer contains junk!
// However, (since timer is declared as __block) we can give it a meaningful value now and have it updated inside of the block, as well:
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerHandler:) userInfo:timerBlock repeats:YES];
}
-(void)timerHandler:(NSTimer*)timer
{
((void (^)())[timer userInfo])(); // retrieve the block and run it
}
Caveat:
Since I'm sending this from my phone, I am not 100% sure about the cast in timerHandler:. But it's something along this line...
You should be able to omit the cast altogether, but will definitely see a warning then.

Create a SEL with an object passed into it

I know how to use:
[self method:object];
But is it possible to get a SEL object of this?
SEL method = #selector(method:object);
Doesn't work.
Thanks :)
A SEL is just the selector - the name of the message that's sent. To capture a specific instance of that message, its arguments, and its return value as an object, you need to use NSMethodSignature and NSInvocation. An example, based on your hypothetical -method:object above:
NSMethodSignature *sig = [SomeClass instanceMethodSignatureForSelector:#selector(method:)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
// Assume that someObject is an instance of SomeClass
[inv setTarget:someObject];
// Assume an "id object" declared elsewhere.
// Also note that self & _cmd are at indices 0 & 1, respectively
[inv setArgument:&object atIndex:2]
// Some time later...
[inv invoke];
Note that, because an NSInvocation is an object, it doesn't have to be invoked immediately. It can be stored for later use, and usually is - there are far easier ways to send a message if one wants to do so immediately. Cocoa's standard undo/redo machinery, for example, is based on storing and invoking NSInvocations.
A #selector is something that is of another method or function.
Take this for an example:
-(IBAction)timerStart {
timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(targetMethod:)
userInfo:nil
repeats:NO];
}
-(void)targetMethod:(id)sender {
[timer invalidate];
timer = nil;
}
As you can see, the selector (targetMethod:) is being called to action after two seconds of the NSTimer is run. The targetMethod: is a (void)function:(id)sender and therefore that is run.
In your case, what I think you're trying to accomplish is
[self performSelector:#selector(methodName:)];

Calling delayed performSelector: on a static instance from an NSThread?

HI, I have a static NSMutableArray* staticArray in an NSOperation subclass (in myOperation.m) and a method:
static NSMutableArray *staticArray =
nil;
+(void) initialize {
staticArray = [[NSMutableArray alloc] init];
}
-(void) addStrToStaticArray:(NSString*)aStr {
if([staticArray indexOfObject:aStr] == NSNotFound) {
[staticArray addObject:aStr];
[staticArray performSelector:#selector(removeObject:)
withObject:aStr
afterDelay:30.];
}
}
I call the above method and after that the operation finishes execution. The problem is that aStr is never removed from the array. What am I missing ? Thanks...
Based to Justin suggestions, I can now delayed remove an object from an array invoking the method from inside a NSThread, NSOperation despite their existence at the time of the removal:
NSMethodSignature * mySignature = [NSMutableArray instanceMethodSignatureForSelector:#selector(removeObject:)];
NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature:mySignature];
[myInvocation setTarget:staticArray];
[myInvocation setSelector:#selector(removeObject:)];
[myInvocation setArgument:&aStr atIndex:2];
//At this point, myInvocation is a complete object, describing a message that can be sent.
NSTimer *timer = [NSTimer timerWithTimeInterval:90.
invocation:myInvocation
repeats:NO];
if(timer) {
NSRunLoop *mainRL = [NSRunLoop mainRunLoop];
[mainRL addTimer:timer forMode:NSDefaultRunLoopMode];
}
The aStr will be removed from staticArray after 90 seconds. For details...
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DistrObjects/Tasks/invocations.html
use timer (CFRunLoopTimer/NSTimer) and run loop (CFRunLoop/NSRunLoop) apis to accomplish this.
in that case, you create a timer, and add it to the main run loop.
this would also require that you create a function or method for your timer to call. if you choose a method, you could use an NSInvocation instead (if that's what you prefer).
since the data is static, and the operation (presumably) won't exist, you can message via a class method.

Objective C: Call variable from another method

I'm trying to access an NSTimer in a method called getTimer in another method.
- (NSTimer *)getTimer{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector: #selector(produceBricks) userInfo:nil repeats:YES];
return timer;
[timer release];
}
I'm trying to stop the timer in another method (a method that would pause the game) by using:
if ([getTimer.timer isValid]) {
[getTimer.timer invalidate];
}
I'm assuming this is not the correct syntax being it tells me getTimer is undeclared. How would I access the timer so I can stop it?
getTimer is a method, not an object, so you can't send messages to it or access properties. Rather, assuming that the method is in the same class as the one calling it, you would call it like this:
NSTimer *timer = [self getTimer];
if ([timer isValid]) [timer invalidate];
//...
Also, you're trying to release your timer in the getTimer method after the return statement. This code will never be executed (the method has already ended) - which is good in this case, because you shouldn't release the timer, it's already autoreleased. I'd recommend that you read something on Objective-C memory management and naming conventions.
Make the timer an instance variable instead of creating in within getTimer. Then it will be accessible anywhere within the class as follows:
in MyClass.h
NSTimer* timer;
I would implement a startTimer and stopTimer method.
- (void) startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector: #selector(produceBricks) userInfo:nil repeats:YES];
}
- (void) stopTimer {
if([timer isValid]) {
[timer invalidate];
}
}

Problems invalidating & re-creating NSTimer(s)

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