NSTimer with block -- am I doing it right?
Asked Answered
H

5

15

The following is my Objective-C category on NSTimer to do block-based firing of NSTimers. I can't see anything wrong with it, but what I am getting is that the block I pass into the schedule... method is being deallocated despite me calling copy on it.

What am I missing?

typedef void(^NSTimerFiredBlock)(NSTimer *timer);

@implementation NSTimer (MyExtension)

+ (void)timerFired:(NSTimer *)timer 
{
    NSTimerFiredBlock blk = timer.userInfo;
    if (blk != nil) {
        blk(timer);
    }
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds 
                                    repeats:(BOOL)repeats 
                                   callback:(NSTimerFiredBlock)blk 
{
    return [NSTimer scheduledTimerWithTimeInterval:seconds
                                            target:self
                                          selector:@selector(timerFired:)
                                          userInfo:[blk copy]
                                           repeats:repeats];
}

@end
Hanway answered 23/2, 2012 at 2:44 Comment(7)
I think (void)timerFired:(NSTimer *)timer should be an instance method -, not a class method +. The same probably applies to scheduledTimerWithTimeInterval, but I am less sure about that.Erythrocytometer
That would not be the case. NSTimer scheduled methods are class methods.Hanway
Passing self as the target strongly suggests an instance method. All code samples of NSTimer that I've seen use instance methods for selectors as well.Erythrocytometer
Class objects are first class objects in Objective-C. No reason why "self" can't point to a class. That you have not seen it does not mean it is not supported. See here.Hanway
Just curious, if you pass something other than a block (say, an instance of your own object with dealloc) for userInfo, does your dealloc gets called?Erythrocytometer
I tried your code, and it works like a charm. I put a [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES callback:^(NSTimer* t){ NSLog(@"I'm here"); }]; call in my application:didFinishLaunchingWithOptions:, and I am getting the I'm here logs every second.Erythrocytometer
Works great, definitely odd calling with self, but it is actually fine.Fabrizio
C
35

I found this code over at http://orion98mc.blogspot.ca/2012/08/objective-c-blocks-for-fun.html

Great work

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.7
      target:[NSBlockOperation blockOperationWithBlock:^{ /* do this! */ }]
      selector:@selector(main)
      userInfo:nil
      repeats:NO
];
Clingstone answered 16/7, 2014 at 20:51 Comment(1)
Wow, this is great. From experimentation, if I don't hold a reference to the NSTimer, it looks it lives until it fires and then gets cleaned up.Giorgia
N
8

You have a project on github that does the job !

Cocoapod BlocksKit, allow you to Blockify a bunch of Classes...

#import "NSTimer+BlocksKit.h"
[NSTimer bk_scheduledTimerWithTimeInterval:1.0 block:^(NSTimer *time) {
       // your code
    } repeats:YES];
Naarah answered 22/4, 2013 at 14:38 Comment(0)
R
3

Here is the Swift version of Mc.Stever's answer:

NSTimer.scheduledTimerWithTimeInterval(0.7, target: NSBlockOperation(block: {
    /* do work */
}), selector: "main", userInfo: nil, repeats: false)
Rudbeckia answered 31/1, 2016 at 16:45 Comment(1)
Swift 2.2: NSTimer.scheduledTimerWithTimeInterval(0.7, target: NSBlockOperation(block: { /* do work */ }), selector: #selector(NSOperation.main), userInfo: nil, repeats: false)Boehmite
D
2

What you're missing is that if the block you're passing in is on the stack then copy will do exactly what the name says — it'll create a copy of the block over on the heap. You'd therefore expect no change in the behaviour of the one you passed in; nobody new is retaining it. The copy will stay alive while the original is deallocated.

(aside: if you're not using ARC you'll also want to autorelease the copy; you're meant to pass a non-owning reference as userInfo:. Otherwise the copy will never be deallocated)

Drapery answered 13/11, 2012 at 0:12 Comment(0)
T
0

try this

typedef void(^NSTimerFiredBlock)(NSTimer *timer);

@interface NSObject (BlocksAdditions)

- (void)my_callBlock:(NSTimer *)timer;

@end

@implementation NSObject (BlocksAdditions)

- (void)my_callBlock:(NSTimer *)timer {
    NSTimerFiredBlock block = (id)self;
    block(timer);
}

@implementation NSTimer (MyExtension)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds 
                                    repeats:(BOOL)repeats 
                                   callback:(NSTimerFiredBlock)blk 
{
    blk = [[blk copy] autorelease];
    return [NSTimer scheduledTimerWithTimeInterval:seconds
                                            target:blk
                                          selector:@selector(my_callBlock:)
                                          userInfo:nil
                                           repeats:repeats];
}

@end
Tantalite answered 23/2, 2012 at 4:13 Comment(2)
Actually, I tried this solution and I m still getting the same problem. I don't think it's that selectors are not supported on class methods. Class objects are also first class objects in Objective-C.Hanway
selectors definitely support class methods. your problem is block get deallocated before timer fired but i cannot see why this is happening. what is the error message?Tantalite

© 2022 - 2024 — McMap. All rights reserved.