2

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;

    }
Wain
  • 118,658
  • 15
  • 128
  • 151
Tomoko Yamaguchi
  • 487
  • 1
  • 6
  • 21

3 Answers3

4

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.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • Or use `NSTimer`'s user info – Scott Berrevoets Apr 26 '14 at 15:01
  • @ScottBerrevoets, you should be a bit careful doing that as you need to copy blocks and the `userInfo` is simply retained... – Wain Apr 26 '14 at 15:02
  • thank you very much for this, ok, I can call another method in CounterClass (every three seconds), which will call the desired target method (every three seconds), but I'm a bit lost by your remark "The block could be stored as a @property of the class". I assume you mean as a property of CounterClass, but how do I get the self.count from CounterClass back in the ViewController (the point of my question). I thought I might need to pass a block to get self.count back from CounterClass. Can you expand a bit to show me what you mean? Thanks if you have time – Tomoko Yamaguchi Apr 26 '14 at 15:18
  • If you have a timer in one class and you want to update another class periodically then you should use a delegate relationship, so, when the timer fires you calculate the new information and call the delegate. You can implement that using a block. The class with the timer has an @property in the .h file for a block that will be called each timer the timer fires. – Wain Apr 26 '14 at 15:23
0

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];
    });
}
lucianomarisi
  • 1,552
  • 11
  • 24
  • This is very interesting I didn't realize there was a way to do it without using NSTimer. Three questions 1) but can you show me what to put where you comment `//your block code` so that I can get the self.count back? 2) and where would I actually do the increment self.count +=1? and 3) if you call [self incrementCountWithCompletion:block] at the end of that same method won't it just keep running over and over again? – Tomoko Yamaguchi Apr 26 '14 at 15:27
  • 1)Not sure I understand what you mean since its you code, I think you may want to have return parameter in block, maybe amend you question 2)You can put that in the dispatch_after block as you did with your method. 3)Yes, that is the repeats part. – lucianomarisi Apr 26 '14 at 15:40
-1

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?

Community
  • 1
  • 1
JonasG
  • 9,274
  • 12
  • 59
  • 88
  • Thank you so much for this. Even with the link, I'm a little confused. I want to use the information (i.e. the self.count) from CounterClass in the ViewController where I call it. What would I put in `your block copy` to do that? and I don't get why you do this? ` void (^completion)(void) = timer.userInfo[@"completion"];` it looks like your assigning the completion block in the userInfo to void(^completion)(void). Can you expand a little with code that could get mine working, maybe I'll understand better. – Tomoko Yamaguchi Apr 26 '14 at 15:14
  • You're adding the block to the userInfo when initializing the timer, make sure you read the first line of code in my answer completely! – JonasG Apr 26 '14 at 15:19
  • I think I saw what your referring to, but I didn't understand it, in the sense that I didn't know what block to add there to get self.count back in the incrementCount method. I get that timer.userInfo[@"completion"]; will call whatever block I supply, but I also don't understand how assigning it to void(^completion)(void) gets self.count back in the ViewController. Thanks if you can explain but if not, I'll meditate on it :) – Tomoko Yamaguchi Apr 26 '14 at 15:24