30

I am trying to pass data through userInfo for an NSTimer call. What is the best way to do this? I am trying to use an NSDictionary, this is simple enough when I have Objective-C objects, but what about other data? I want to do something like this, which doesn't work as is:

- (void)play:(SystemSoundID)sound target:(id)target callbackSelector:(SEL)selector
{
    NSLog(@"pause ipod");
    [iPodController pause];
    theSound = sound;

    NSMutableDictionary *cb = [[NSMutableDictionary alloc] init];
    [cb setObject:(id)&sound forKey:@"sound"];
    [cb setObject:target forKey:@"target"];
    [cb setObject:(id)&selector forKey:@"selector"];

    [NSTimer scheduledTimerWithTimeInterval:0
                                     target:self
                                   selector:@selector(notifyPause1:)
                                   userInfo:(id)cb
                                    repeats:NO];
}
Thromordyn
  • 1,581
  • 4
  • 17
  • 45
zorro2b
  • 2,227
  • 4
  • 28
  • 45

6 Answers6

53

You have to wrap the information correctly into the dictionary:

- (void) play:(SystemSoundID)sound target:(id)target callbackSelector:(SEL)selector
{
    NSLog(@"pause ipod");
    [iPodController pause];
    theSound = sound;

    NSMutableDictionary *cb = [[NSMutableDictionary alloc] init];
    [cb setObject:[NSNumber numberWithInt:sound] forKey:@"sound"];
    [cb setObject:target forKey:@"target"];
    [cb setObject:NSStringFromSelector(selector) forKey:@"selector"];

    [NSTimer scheduledTimerWithTimeInterval:0
                                     target:self
                                   selector:@selector(notifyPause1:)
                                   userInfo:cb 
                                     repeats:NO];
    [cb release];

}

In notifyPause1:, you retrieve everything:

- (void)notifyPause1:(NSTimer *)timer {
    NSDictionary *dict = [timer userInfo];

    SystemSoundID sound = [[dict objectForKey:@"sound"] intValue];
    id target = [dict objectForKey:@"target"];
    SEL selector = NSSelectorFromString([dict objectForKey:@"selector"]);

    // Do whatever...
}

As the timer is a repeating timer, you do not need the dictionary anymore, so you can release it.

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
Laurent Etiemble
  • 27,111
  • 5
  • 56
  • 81
  • A heads up - if you do call *invalidate* for your NSTimer, make sure to check *isValid* flag before accessing *userInfo*. This is stated in the docs for *userInfo* property, and I just got caught a crash in that exact place. – nalexn Oct 11 '17 at 17:04
7

Your call is right, but you don't have to cast the dictionary to id. You can get the userInfo back with the following line in your notifyPause1: method:

- (void)notifyPause1:(NSTimer *)timer {

    NSDictionary *dict = [timer userInfo];

}
schaechtele
  • 1,130
  • 1
  • 9
  • 12
6

You may ask for the timer in the method given to the selector,

Then you can grab useInfo from that timer (timer.userInfo):

- (void)settingTimer
{
[NSTimer scheduledTimerWithTimeInterval:kYourTimeInterval
                                 target:self
                               selector:@selector(timerFired:)
                               userInfo:yourObject
                                repeats:NO];
}

- (void)timerFired:(NSTimer*)theTimer
{
  id yourObject = theTimer.userInfo;
  //your code here
}
Avishay Cohen
  • 2,418
  • 1
  • 22
  • 40
2

Laurent Etiemble's method works well for making good use of timer methods in subclasses of UIViewcontroller which can be easily used in a number of different view controllers.

Below is a way to write a generic score display which can be used over and over in the same app by passing the view through NSTimer userInfo.

.h (subclass of UIViewcontroller)

  - (NSTimeInterval) timeSet;
  - (void) timeRun: (UISegmentedControl*)scoreDisplay;
  - (void) timeWrite: (NSTimer *)timer;

.m

            - (NSTimeInterval) timeSet {
                return [self.startTime timeIntervalSinceNow];
            }

            - (void) timeWrite: (NSTimer *)timer
            {
                NSDictionary *dict = [timer userInfo];
                UISegmentedControl *scoreDisplay = [dict objectForKey:@"scoreDisplay"];

                int myint = round([self timeSet]);
                NSString* buttonWrite = [[[NSString stringWithFormat:@"Score: %d", self.answered] stringByAppendingString:[NSString stringWithFormat:@" Errors: %d", self.errors]] stringByAppendingString:[NSString stringWithFormat:@" Time: %d", (myint*-1)]];

                [scoreDisplay setTitle:buttonWrite forSegmentAtIndex:0];
            }

            - (void) timeRun: (UISegmentedControl*)scoreDisplay
            {
                self.startTime = [NSDate date];

                NSMutableDictionary *cb = [[NSMutableDictionary alloc] init];
                [cb setObject:scoreDisplay forKey:@"scoreDisplay"];

                self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                                target:self
                                                              selector:@selector(timeWrite:)
                                                              userInfo:cb
                                                               repeats:YES];

            }

In the view controller

.m

  - (void)viewDidLoad
  {
       [super viewDidLoad];

       //load ScoreDisplay
       [self timeRun:self.scoreDisplay];

  }
MGM
  • 2,379
  • 1
  • 19
  • 13
2

sound and selector aren't Objective-C objects: sound is an unsigned number and selector is a pointer to a C struct . That's likely to cause a crash of some sort.

You'll want to use NSValue to hold the value for selector and NSNumber to hold the value for sound. NSValue and NSNumber are objects and will work with the NSMutableDictionary.

Giao
  • 14,725
  • 2
  • 24
  • 18
0

You should not cast your objects to id when assigning to the dictionary. Any object that can be embedded directly into a NSDictionary already derives from NSObject and are implicitly concidered cast to id. Store the name of the selector as an NSString (using NSStringFromSelector()) and then convert it back to a selector using NSSelectorFromString()

Claus

Claus Broch
  • 9,212
  • 2
  • 29
  • 38