NSTimer problem

  • I've found some issues with NSTimer and I could use a little help. I've
    been doing some testing with it and I have some odd behavior. I initialize
    a timer thusly:
    -(IBAction)resetTimer:(id)sender{

        if([self timer])
            [[self timer] invalidate];

        timer = [[NSTimer alloc] initWithFireDate:[NSDate
    dateWithTimeIntervalSinceNow:currentInterval]
                                        interval:currentInterval target:self
    selector:@selector(timerCalled:)
    userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        [self setStringValue:[[timer fireDate] description]];
        [timer fire];
        [self setTimer:timer];
    }

    -(void)timerCalled:(id)sender{
        if([self timer]){
            NSLog(@"Fire date: %@", [[[self timer] fireDate] description]);
        }
        else
            NSLog(@"Timer not valid!");
    }
    Store the timer in a instance variable and use accessors to manipulate it.

    -(void)setTimer:(NSTimer*)tm{

        [timer autorelease];
        timer = [tm retain];
    }
    -(NSTimer*)timer{
        return timer;
    }

    I have a window with a button that when clicked calls an IBAction that
    invalidates the timer. When I click the button, the function is invoked(I
    have an NSLog statement there), but the timer keeps running. And the
    firedate matches the timestamp for the log entry. I would expect it to
    display the next fire date.
    Stepping through the debugger, I see that when inside my invalidate
    function, the timer is null, 0x0.

    -(IBAction)stopTimer:(id)sender{
        NSLog(@"Calling invalidate");
        if([timer isValid])
            [timer invalidate];
        [self resetInterval:nil];
    }

    I also have a matrix that when clicked, changes the repeat interval to the
    tag value associated with one of its two buttons, (I set custom tag values
    instead of the default 0, 1) and then restarts the timer.
    -(IBAction)resetInterval:(id)sender{

        if([[sender selectedCell] tag] != currentInterval){
            currentInterval = [[sender selectedCell] tag];
            [self resetTimer:nil];
            count = 0;
        }
    }
    When the matrix is clicked, instead of the first timer being invalidated,
    I get a second timer firing on the new interval. The first timer is still
    invalid, but firing anyway, and I apparently don't have a valid handle on
    the second one as it can't be stopped either. I never have more than two
    timers firing even after clicking the matrix a third or forth time.
    I tried sublassing NSTimer, and overriding release, autorelease, and
    dealloc, just to add log messages. Initializing my custom timer failed. I
    eventually found a post that suggests subclassing NSTimer can't be done.
    http://lists.apple.com/archives/Cocoa-dev/2006/Nov/msg00389.html

    I'm not sure what's going on here. Can someone lend a hand? This is
    running on 10.5.1 PPC. I'm going to do some testing this evening on 10.4
    where I don't recall ever having any trouble with the NSTimer class
    before, so I'm not sure if:
    a) I screwed up (typical)
    b) NSTimer is not quite right on Leopard (possible)
    c) I should just use performSelector... (probably)

    Also, I should note that I've been playing with the code to try to track
    down the problem. What I have written above should be the original code,
    but I'm re-writing it in Thunderbird, so there may be a syntactical error
    here or there. I'm not at a Mac right now, so I can't test it, but it
    should be the original code where I noticed the problem.

    What I'm trying to do is create a timer that will send a given message to
    the specified object at regular intervals. I also need to reset that timer
    periodically as the interval changes.
    Thanks
  • on 1/16/08 3:22 PM, <lorenzo...> purportedly said:

    You are leaking objects like mad.

    > timer = [[NSTimer alloc] initWithFireDate:[NSDate
    > dateWithTimeIntervalSinceNow:currentInterval]
    > interval:currentInterval target:self

    You *do* know that above you are setting your object property, and not a
    local variable?

    > -(void)setTimer:(NSTimer*)tm{
    >
    > [timer autorelease];
    > timer = [tm retain];
    > }

    Here you are retaining an alloced object after autoreleasing it, so it will
    never be released. If you don't know why, re-read the "cocoa memory
    management guidelines" (again?).

    This will probably not solve your problem. It is likely that the problem
    lies in code that you aren't showing, such as how the timer is being created
    in the first place.

    Also:

    > Stepping through the debugger, I see that when inside my invalidate
    > function, the timer is null, 0x0.

    It always will, the first time. Once it has been created, it should no
    longer be null.

    Best,

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"
  • > on 1/16/08 3:22 PM, <lorenzo...> purportedly said:
    >
    > You are leaking objects like mad.
    >
    >> timer = [[NSTimer alloc] initWithFireDate:[NSDate
    >> dateWithTimeIntervalSinceNow:currentInterval]
    >> interval:currentInterval
    >> target:self
    >
    > You *do* know that above you are setting your object property, and not a
    > local variable?
    >
    >> -(void)setTimer:(NSTimer*)tm{
    >>
    >> [timer autorelease];
    >> timer = [tm retain];
    >> }
    >
    > Here you are retaining an alloced object after autoreleasing it, so it
    > will
    > never be released. If you don't know why, re-read the "cocoa memory
    > management guidelines" (again?).

    timer is an instance variable, not a local variable. Sorry if that wasn't
    clear.
    I have read the guidelines. This one is technique 2 on this page:
    http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Articl
    es/mmAccessorMethods.html#//apple_ref/doc/uid/TP40003539

    But I have used all three listed on the page, with 3 being the most
    typical for my uses. If I understand the process correctly, the
    autorelease tells the runtime to decrement the retain at some point in
    future, most likely after the function returns. The retain increments the
    retain count immediately, so "at point in the future" there should an
    offset in retain/releases and the retain count for my object should be
    correct. Am I misunderstanding something? This is important to me since I
    have quite a bit of code written like this and I just don't find leaks
    using tools such as instruments now or in the past with leaks or
    ObjectAlloc.
    >
    > This will probably not solve your problem. It is likely that the problem
    > lies in code that you aren't showing, such as how the timer is being
    > created
    > in the first place.
    >
    > Also:
    >
    >> Stepping through the debugger, I see that when inside my invalidate
    >> function, the timer is null, 0x0.
    >
    > It always will, the first time. Once it has been created, it should no
    > longer be null.

    So how can I get a valid reference to the timer?
    >
    > Best,

    Thanks for the reply, I'll keep working with it and read the MM guide again.
    >
    > Keary Suska
    > Esoteritech, Inc.
    > "Demystifying technology for your home or business"
    >
  • On Jan 17, 2008 6:21 PM,  <lorenzo...> wrote:

    > timer is an instance variable, not a local variable. Sorry if that wasn't
    > clear.

    Why, then, in resetTimer, do you set its value directly:

      timer = [[NSTimer alloc] initWithFireDate:[NSDate ...

    And then later in the same block call:

      [self setTimer:timer];

    ??!

    Hamish
  • on 1/17/08 11:21 AM, <lorenzo...> purportedly said:

    > timer is an instance variable, not a local variable. Sorry if that wasn't
    > clear.

    It was clear in the code, just that the coding conventions you are using
    are, well, unorthodox. These could come back to bite you.

    > I have read the guidelines. This one is technique 2 on this page:
    > http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Articl
    es/

    > mmAccessorMethods.html#//apple_ref/doc/uid/TP40003539
    > But I have used all three listed on the page, with 3 being the most
    > typical for my uses. If I understand the process correctly, the
    > autorelease tells the runtime to decrement the retain at some point in
    > future, most likely after the function returns. The retain increments the
    > retain count immediately, so "at point in the future" there should an
    > offset in retain/releases and the retain count for my object should be
    > correct. Am I misunderstanding something? This is important to me since I
    > have quite a bit of code written like this and I just don't find leaks
    > using tools such as instruments now or in the past with leaks or
    > ObjectAlloc.

    I think you mean technique 2. Anyway, the problem is not with your accessor,
    but with your reset method, where you alloc the timer instance variable, an
    then pass it to the setter. If you use that pattern frequently, you probably
    have a big mess.

    In you specific code, when you alloc the timer, the object has a retain
    count of 1. Naturally. Then you call setTimer:, and that method autoreleases
    the timer instance. Cool--that offsets the alloc. But then you retain the
    timer object, which at that point increases the retain count to 2.
    Whoops--now when the autorelease pool is cleared, your object as a retain
    count of 1 and will not be released. Hence, it leaks. This happens because
    you are alloc/initing the instance variable, and then passing it to
    setTimer. Thus, in setTimer, the argument "tm" and the variable "timer" are
    exactly the same object.

    Also, AFAIK, you don't use ObjectAlloc for leaks, you use MallocDebug. I
    don't know about Instruments, but if it is designed to detect leaks, and it
    doesn't detect any, there must be a convolution in your code that the tool
    can't deal with. I can tell you for certain that after the first call to
    -resetTimer:, every subsequent call is leaking exactly one NSTimer object.

    >> It always will, the first time. Once it has been created, it should no
    >> longer be null.
    >
    > So how can I get a valid reference to the timer?

    I don' understanding what you are asking. When your object is first created,
    all instance variables that are objects are set to nil. At some point, the
    timer property is set with a valid object. Until that point, there will be
    no valid object. Why would you expect otherwise?

    Best,

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"
previous month january 2008 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Go to today