TargetOwnedTimer.m   [plain text]


/*
 * TargetOwnedTimer.m
 *
 * $Header$
 *
 * Copyright 2004 Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 * require a specific license from the United States Government.
 * It is the responsibility of any person or organization contemplating
 * export to obtain such a license before exporting.
 * 
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */

#import "TargetOwnedTimer.h"

// Kerberos.app has a bunch of objects which are created by the main controller
// (either directly or indirectly) and released when the controller no longer 
// needs them.  Some of these objects want NSTimers -- and they want the NSTimer
// invalidated when they are released by the controller.
//
// Unfortunately, NSTimers retain their targets. As a result, if the objects were
// directly instantiating the timers, they would never get freed when released 
// since the timer retained the object.  Yay for circular dependencies created 
// by refcounted objects!  Woooo!
//
// This isn't inherently wrong.  Owning the object you plan to call back into is 
// safer because you know it will exist when you call into it.  And depending on 
// how your application works, you might actually want your objects to persist 
// until the timer fires and then be released.  
//
// Obviously we could create invalidate methods for every Kerberos object that needs
// to create a timer and then call the timer's invalidate method from there, but 
// that sort of defeats the usefulness of having refcounted objects in the first 
// place.  And it's prone to simple logic errors which cause silent memory leaks
// since a number of the aforementioned objects are in NSArrays and are only
// freed as a side effect of being removed from the array.
//
// Instead, here is a TargetOwnedTimer class which encapsulates an NSTimer so that  
// the real NSTimer can retain this class.  This reduces the above problem to just
// having to remember to invalidate the TargetOwnedTimer before releasing it in the
// real object's dealloc method (which is a lot easier to remember and verify).  
// When the real object dealloc method is called, it can manually deallocate the 
// TargetOwnedTimer using invalidate (removing the refcount) followed by release.
//
// Basically this just makes timer memory management work like it does for 
// NSNotifications.


@implementation TargetOwnedTimer

// ---------------------------------------------------------------------------

+ (TargetOwnedTimer *) scheduledTimerWithTimeInterval: (NSTimeInterval) seconds 
                                               target: (id) target 
                                             selector: (SEL) selector 
                                             userInfo: (id) userInfo 
                                              repeats: (BOOL) repeats
{
    TargetOwnedTimer *toTimer = [[TargetOwnedTimer alloc] initWithTimeInterval: seconds 
                                                                        target: target 
                                                                      selector: selector 
                                                                      userInfo: userInfo 
                                                                       repeats: repeats];
    [[NSRunLoop currentRunLoop] addTimer: [toTimer timer] forMode: NSDefaultRunLoopMode];
    return [toTimer autorelease];
}

// ---------------------------------------------------------------------------

+ (TargetOwnedTimer *) scheduledTimerWithFireDate: (NSDate *) fireDate
                                         interval: (NSTimeInterval) seconds 
                                           target: (id) target 
                                         selector: (SEL) selector 
                                         userInfo: (id) userInfo 
                                          repeats: (BOOL) repeats
{
    TargetOwnedTimer *toTimer = [[TargetOwnedTimer alloc] initWithFireDate: fireDate
                                                                  interval: seconds 
                                                                    target: target 
                                                                  selector: selector 
                                                                  userInfo: userInfo 
                                                                   repeats: repeats];
    [[NSRunLoop currentRunLoop] addTimer: [toTimer timer] forMode: NSDefaultRunLoopMode];
    return [toTimer autorelease];
}

// ---------------------------------------------------------------------------

- (id) initWithTimeInterval: (NSTimeInterval) seconds 
                     target: (id) target 
                   selector: (SEL) selector 
                   userInfo: (id) userInfo 
                    repeats: (BOOL) repeats
{
    if ((self = [super init])) {
        timerTarget = target;
        timerSelector = selector;
        timerUserInfo = userInfo;
        
        dprintf ("TargetOwnedTimer for %lx initializing", (long) timerTarget);        
        
        timer = [[NSTimer timerWithTimeInterval: seconds
                                        target: self
                                      selector: @selector (timer:)
                                      userInfo: NULL
                                       repeats: repeats] retain];
        if (!timer) {
            [self release];
            return NULL;
        }
    }
    return self;
}

// ---------------------------------------------------------------------------

- (id) initWithFireDate: (NSDate *) fireDate
               interval: (NSTimeInterval) seconds
                 target: (id) target 
               selector: (SEL) selector 
               userInfo: (id) userInfo 
                repeats: (BOOL) repeats
{
    if ((self = [super init])) {
        timerTarget = target;
        timerSelector = selector;
        timerUserInfo = userInfo;
        
        dprintf ("TargetOwnedTimer for %lx initializing", (long) timerTarget);        
        
        timer = [[NSTimer alloc] initWithFireDate: fireDate
                                        interval: seconds
                                           target: self
                                         selector: @selector (timer:)
                                         userInfo: NULL
                                          repeats: repeats];
        if (!timer) {
            [self release];
            return NULL;
        }
    }
    return self;
}

// ---------------------------------------------------------------------------

- (void) dealloc
{
    dprintf ("TargetOwnedTimer for %lx deallocating", (long) timerTarget);        
    [timer release];
    [super dealloc];
}

// ---------------------------------------------------------------------------

- (NSTimer *) timer
{
    return timer;
}

// ---------------------------------------------------------------------------

- (void) invalidate
{
    // Call before releasing TargetOwnedTimer so its refcount goes to 0
    [timer invalidate];
}

// ---------------------------------------------------------------------------

- (BOOL) isValid
{
    return [timer isValid];
}

// ---------------------------------------------------------------------------

- (void) fire
{
    [timer fire];
}

// ---------------------------------------------------------------------------

- (NSDate *) fireDate
{
    return [timer fireDate];
}

// ---------------------------------------------------------------------------

- (void) setFireDate: (NSDate *) date
{
    [timer setFireDate: date];
}

// ---------------------------------------------------------------------------

- (NSTimeInterval) timeInterval
{
    return [timer timeInterval];
}


// ---------------------------------------------------------------------------

- (id) userInfo
{
    return timerUserInfo;
}

// ---------------------------------------------------------------------------

- (void) timer: (NSTimer *) timer
{
    [timerTarget performSelector: timerSelector withObject: self];
}

@end