views:

852

answers:

3

I'm working through Cocoa Programming for Mac OS X (3rd ed) and in chapter 4 I wrote this app:

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    //create the date object
    NSCalendarDate *now = [[NSCalendarDate alloc] init];

    //seed random # generator
    srandom(time(NULL));

    NSMutableArray *array;
    array = [[NSMutableArray alloc] init];
    int i;
    for (i=0; i<10; i++){
     //create a date/time that is 'i' weeks from now
     NSCalendarDate *iWeeksFromNow;
     iWeeksFromNow = [now dateByAddingYears:0
             months:0
               days:(i*7)
              hours:0
               minutes:0
               seconds:0];

     //create a new instance of lottery entry
     LotteryEntry *entry = [[LotteryEntry alloc] init];
     [entry setEntryDate:iWeeksFromNow];

     [array addObject:entry];
     [entry release];
    }
    [now release];
    now = nil;

    for (LotteryEntry *entryToPrint in array) {
     NSLog(@"%@", entryToPrint);
    }
    [array release];
    array = nil;

    NSLog(@"about to drain the pool... (%@)", pool);
    [pool drain];
    NSLog(@"done");
    NSLog(@"GC = %@", [NSGarbageCollector defaultCollector]);
    return 0;
}

The LotteryEntry class looks like this:

@implementation LotteryEntry

- (void)setEntryDate:(NSCalendarDate *)date
{
    entryDate = date;
}

- (NSCalendarDate *)entryDate
{
    return entryDate;
}

- (int)firstNumber
{
    return firstNumber;
}

- (int)secondNumber
{
    return secondNumber;
}

- (id)init
{
    return [self initWithDate:[NSCalendarDate calendarDate]];
}

- (id)initWithDate:(NSCalendarDate *)date
{
    if(![super init])
     return nil;

    NSAssert(date != nil, @"Argument must be non-nil");

    firstNumber = random() % 100 + 1;
    secondNumber = random() % 100 + 1;
    entryDate = [date retain]; 
    return self;
}

- (NSString *)description
{
    NSString *result;
    result = [[NSString alloc] initWithFormat:@"%@ = %d and %d",
        [entryDate descriptionWithCalendarFormat:@"%b %d %Y"],
        firstNumber,
        secondNumber];
    return result;
}

- (void)dealloc
{
    NSLog(@"deallocating %@", self);
    [entryDate release];
    [super dealloc];
}

@end

As you can see I'm retaining and releasing the objects here. I'm pretty sure my code matches the book's, however when I run the app, at the [pool drain] I get this message:

Program received signal: “EXC_BAD_ACCESS”.

I'm not sure what's causing this. I'm hoping it's something stupid that I missed, but I'd sure appreciate a few other pairs of eyes on it. Thanks in advance!

(side note: I'm a .NET developer, so ref counting is pretty foreign to me!)

+2  A: 

doh! Just typing the code made me realize my problem. Doncha love it when that happens?

I am retaining the date in my init, but I still had that extra setEntryDate method that was not calling retain. Removing this and calling the initWithDate method instead seemed to fix the problem.

Ben Scheirman
+1  A: 

It's not related to your issue at hand but you should avoid using NSCalendarDate, it's been deprecated for a while now and will probably be removed from the API entirely soon.

Ashley Clark
What's the replacement?
Ben Scheirman
NSCalendar, NSDate and NSDateComponents. You can read more about them here: http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendars.html
Ashley Clark
+3  A: 

It also looks like you have a bug in this method:

- (id)initWithDate:(NSCalendarDate *)date
{
    if(![super init])
        return nil;

    NSAssert(date != nil, @"Argument must be non-nil");

    firstNumber = random() % 100 + 1;
    secondNumber = random() % 100 + 1;
    entryDate = [date retain];  
    return self;
}

You are essentially discarding the results from [super init], while it may not be a problem in this instance, it could cause serious problems in others. You should 'always' structure you init methods like this:

- (id)initWithDate:(NSCalendarDate *)date
{
    if(self = [super init]) {
        NSAssert(date != nil, @"Argument must be non-nil");

        firstNumber = random() % 100 + 1;
        secondNumber = random() % 100 + 1;
        entryDate = [date retain];
}
return self;

If you are not going to return self from an init method (for instance it is a factory or something odd like that), you should remember to release self. It has been alloc'ed, and if you don't return it, it cannot be released properly. Example:

- (id) init
{
    NSObject*    newSelf = [[NSObject alloc] init];

    [self release];
    return newSelf;
}
Thanks for the tips, that makes sense
Ben Scheirman