I'm using a basic timer that calls this method:
- (void) refresh:(id)obj
{
if (obj == YES) doSomething;
}
I want to call this method from certain areas of my code and also from a timer
[NSTimer scheduledTimerWithTimeInterval:refreshInterval
target:self
selector:#selector(refresh:)
userInfo:nil
repeats:YES];
When I put YES as the argument for the userInfo parameter, I get an EXC_BAD_ACCESS error; why is this?
Can someone help me do this the right way so that there is no ugly casting and such?
The userInfo parameter must be an object; it is typed id. YES is a primitive, namely the value 1. In order to make sure the userInfo object does not get deallocated, the timer retains it. So, when you passed YES, NSTimer was doing [(id)YES retain]. Try that in your own code and see what happens. :-P
As the Documentation states, the selector you give the method must have the signature
- (void)timerFireMethod:(NSTimer*)theTimer
This means you can't have an NSTimer invoke just any method—not directly at least. You can make a special method with the above signature which in turn invokes any method you want though.
So, say you have a method called refresh:, and you want to call it every so often, passing it YES. You can do this like so:
// somewhere
{
[NSTimer scheduledTimerWithTimeInterval:refreshInterval
target:self
selector:#selector(invokeRefresh:)
userInfo:nil
repeats:YES];
}
- (void)invokeRefresh:(NSTimer *)timer {
[self refresh:YES];
}
- (void)refresh:(BOOL)flag {
if (flag) {
// do something
}
}
In Objective-C, a primitive type is not an object. So you can't directly pass it to an argument which expects an id, which stands for a generic object. You need to wrap it into an NSNumber object.
Use
NSTimer*timer=[NSTimer scheduledTimerWithTimeInterval:refreshInterval
target:self
selector:#selector(refresh:)
userInfo:[NSNumber numberWithBool:YES]
repeats:YES];
and
- (void) refresh:(NSTimer*)timer
{
NSNumber* shouldDoSomething=[timer userInfo];
if ([shouldDoSomething boolValue]) doSomething;
}
Don't forget to invalidate and release the timer once it's done.
By the way, you don't have to compare a BOOL (or C++ bool) against YES or true or whatever. When you write
if(a>b) { ... }
a>b evaluates to a bool, and if uses the result. What you're doing there is like
if((a>b)==YES) { ... }
which is quite strange to me. It's not that the (..) after if should contain a comparison; it should contain a bool.
As a follow-up to kperryua's answer, if you want to pass a primitive through userInfo you can box it with NSNumber or NSValue; in the case of a boolean, you'd want to use [NSNumber numberWithBool:YES], then call boolValue in the timer callback to get back the primitive.
Related
Where this:
distanceTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(applyShot:newPositionOne.x with:newPositionOne.y) userInfo:nil repeats:NO];
^^ This doesn't work.
Must equal this
[self applyShot:newPositionOne.x with:newPositionOne.y];
I basically need a delay before running this method, and it passes the variables because they'll be different by the time the method runs, so it has to remember them somehow.
However, I cannot for the life of me figure out how to pass variables in an #selector.
I've done it before with button.tag for example, but never for this.
Any help will be appreciated, thank you.
I understand I can just set global variables, but is is possible to pass them?
Ok so there a couple things that you can do. You are correct, it is difficult to pass variables into an #selector() declaration. Here are two options:
1) call a different method (defined in your class) that handles the variables. For example:
distanceTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(applyShot) userInfo:nil repeats:NO];
- (void)applyShot
{
// manipulate variables and do your logic here
}
2) Pass a dictionary into the userInfo argument of:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
This dictionary can look like this:
NSDictionary *params = #{#"newXPosition: #(90), #""newYPosition: #(100)"};
Then just change your applyShot: method to take in an NSDictionary argument, and then parse through the dictionary there to find the relevant values and "apply the shot."
A selector is not a packaged method. A selector is just the name of a message. You can think of it as a string (it used to be implemented as a string).
The traditional way to address this is with an NSInvocation which packages up a complete method call, not just the name of the message. That's well covered by Arguments in #selector. The other way to handle it is to package your options into the userInfo and change your method to read its parameters from that.
But typically the better solution today is to use dispatch_after instead:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^(void){
[self applyShot:newPositionOne.x with:newPositionOne.y];
});
I need to pass an integer to my method startEvent:. The method is a timer's action method. I've read about how to pass data with userInfo, however I don't understand how to do that so I can still call the method as I'd normally do.
How do I pass integers from an NSTimer and from a regular call to the same method?
[NSTimer scheduledTimerWithTimeInterval:0.032f target:self selector:#selector(startEvent:) userInfo:nil repeats:NO];
[self startEvent: 0];
-(void)startEvent:(int) event {
// ...
}
Use a pass-through for the timer's action method:
- (void)startEventFromTimer:(NSTimer *)tim
{
[self startEvent:[[tim userInfo] intValue]];
}
Where your timer was created with an NSNumber for its user info object.
[NSTimer scheduledTimerWithTimeInterval:0.032f
target:self
selector:#selector(startEventFromTimer:)
userInfo:#(theIntYouWantToPass)
repeats:NO];
You are starting from a fundamental misunderstanding. Your method cannot be the timers action method unless it conforms to the documented signature.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/Reference/NSTimer.html
(void)timerFireMethod:(NSTimer *)timer
The only opportunity you have to pass additional information in is via the userinfo object, as Josh Caswells answer demonstrates.
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:)];
This is a really dumb question. How would you pass an object to a method using an NSTimer?
I mean something like this -
I have a method in BigView.m that has a method called doSomethingWithClass:.
- (void)doSomethingWithClass:(CustomClass *)class {
NSLog(#"Something was done");
}
In another class called CustomClass, I have an NSTimer -
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:bigView selector:#selector(doSomethingWithClass:) userInfo:nil repeats:NO];
Where bigView is an instance of BigView. Now I want to pass an entire instance of CustomClass as the parameter in the method doSomethingWithClass:. How do I do it?
If you don't need to refer to the timer, use the simpler performSelector:withObject:afterDelay: method.
[bigView performSelector:#selector(doSomethingWithClass:)
withObject:customClass
afterDelay:0.5];
(To cancel it, use +cancelPreviousPerformRequestsWithTarget:….)
Firstly newbie question: What's the difference between a selector and a method?
Secondly newbie question (who would have thought): I need to loop some code based on instance variables and pause between loops until some condition (of course based on instance variables) is met. I've looked at sleep, I've looked at NSThread. In both discussions working through those options many asked why don't I use NSTimer, so here I am.
Ok so it's simple enough to get a method (selector? ) to fire on a schedule. Problem I have is that I don't know how to see instance variables I've set up outside the timer from within the code NSTimer fires. I need to see those variables from the NSTimer selector code as I 1) will be updating their values and 2) will set labels based on those values.
Here's some code that shows the concept… eventually I'd invalidate the timers based on myVariable too, however I've excluded that for code clarity.
MyClass *aMyClassInstance = [MyClass new];
[aMyClassInstance setMyVariable:0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff) userInfo:nil repeats:YES];
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(doSomeOtherStuff) userInfo:nil repeats:YES];
- (void) doStuff {
[aMyClassInstance setMyVariable:11]; // don't actually have access to set aMyClassInstance.myVariable
[self updateSomeUILabel:[NSNumber numberWithInt:aMyClassInstance.myVariable]]; // don't actually have access to aMyClassInstance.myVariable
}
- (void) doSomeOtherStuff {
[aMyClassInstance setMyVariable:22]; // don't actually have access to set aMyClassInstance.myVariable
[self updateSomeUILabel:[NSNumber numberWithInt:aMyClassInstance.myVariable]]; // don't actually have access to aMyClassInstance.myVariable
}
- (void) updateSomeUILabel:(NSNumber *)arg{
int value = [arg intValue];
someUILabel.text = [NSString stringWithFormat:#"myVariable = %d", value]; // Updates the UI with new instance variable values
}
You can use the userInfo parameter to transmit arbitrary object. In this case, you pass aMyClassInstance as userInfo:
MyClass *aMyClassInstance = [MyClass new];
[aMyClassInstance setMyVariable:0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff) userInfo:aMyClassInstance repeats:YES];
In the timer callback (which MUST takes a parameter), you get back the userInfo from the timer and cast it:
- (void) doStuff:(NSTimer *)timer {
MyClass *instance = (MyClass *)[timer userInfo];
[instance setMyVariable:11];
[self updateSomeUILabel:[NSNumber numberWithInt:instance.myVariable]];
}
The neat thing is that the timer retains the userInfo parameter.
One of your questions was asking about the difference between a selector and a method.
A selector “selects” the method to use from an object. Imagine you had some animal classes, say Dog, Cat, and Bird, all subclasses of Animal. They all implement a method called makeSound. Each class will have its own implementation of makeSound, otherwise all of the animals will sound the same. So, all animals have a different method for making a sound, but you get each animal to make its sound using the same selector. You are selecting the makeSound method of an animal, in other words.
You do have access to instance variables if you set the instance as the target of the timer like so:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:aMyClassInstance selector:#selector(doStuff) userInfo:nil repeats:YES];
The instance (which you've referred to as aMyClassInstance) will be self.
Alternatively you can put aMyClassInstance and any other objects in the userInfo dictionary. You would do that like so:
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
aMyClassInstance, #"a",
bMyClassInstance, #"b",
cMyClassInstance, #"c",
nil];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(doStuff:) userInfo:userInfo repeats:YES];
Then, in the doStuff: selector, you can get them back out like so:
-(void) doStuff:(NSTimer*)timer;
{
MyClass* aMyClassInstance = [[timer userInfo] objectForKey:#"a"];
MyClass* bMyClassInstance = [[timer userInfo] objectForKey:#"b"];
MyClass* cMyClassInstance = [[timer userInfo] objectForKey:#"c"];
//do whatever you want here
}