Trouble With NSDate Usage

Apprentice
Posts: 13
Joined: 2006.09
Post: #1
Hey all,

Being pretty new to Cocoa, I am having a little issue trying to use an NSDate object. Here is what I have:

In Interface
Code:
{
    NSDate *last;
}

In Implementation
Code:
- (id) initMyObject
{
    if (self = [super init])
    {
        last = [[NSDate date] addTimeInterval: -1.0];
        return self;
    }
    else
        NSLog(@"*** self was not properly inited!");
}

- (void) doWork
{
    NSDate *now = [NSDate date];

    NSLog(@"** OUTSIDE TimeIntervalSinceNow: %0.1f",
        fabs([last timeIntervalSinceDate:now]));

    if (fabs([last timeIntervalSinceDate:now]) >= 1.0)
    {
        NSLog(@"** TimeIntervalSinceNow: %0.1f",
            fabs([last timeIntervalSinceDate:now]));

        // doMoreWork
        last = now;
    }
}

Console Output
Code:
[Session started at 2006-09-09 12:42:16 -0500.]
2006-09-09 12:42:17.030 myWorkApp[13970] ** OUTSIDE TimeIntervalSinceNow: 1.1

myWorkApp has exited with status -1.

Ok, so for some reason my app just exits at that point and one of two things happens:

1. After a clean build it stops on "class_initialize".
2. After being ran once and then ran again, without a clean build, it stops on "obj_msgSend".

Any ideas on what I am doing wrong here. Any tips that someone can provide that will help me troubleshoot the issue and possibly find it myself?

All help is appreciated. Thanks.

EDIT:
I believe I solved it. By doing the following it started working. If I am missing something else please let me know.

In initMyObject
last = [[[NSDate date] addTimeInterval: -1.0] retain];

and:

In doWork
NSDate *now = [[NSDate date] retain];
[last release];
last = now;

Gerry

Quote:There is a name for people who are not excited about their work--unemployed.
Quote this message in a reply
Moderator
Posts: 508
Joined: 2002.09
Post: #2
You're not retaining the last variable. If you don't do this it will become garbage when the init method exists.

So you do this:

last = [[NSDate date] retain];
[last addTimeInterval: -1.0];

Read about Cocoa memory management and read it very carefully. To summarize:

if you init alloc, you must release
if you retain, you must release
if you autorelease, you don't do anything
if you want to reuse autoreleased objects, you retain, and release when you are done with them

"When you dream, there are no rules..."
Quote this message in a reply
Moderator
Posts: 508
Joined: 2002.09
Post: #3
Xenos Wrote:In doWork
NSDate *now = [[NSDate date] retain];
[last release];
last = now;

That's a memory leak, you don't need to retain now, since you are only using it in this method.

Instead do this:

NSDate *now = [NSDate date];
[last release];
last = [now retain];

"When you dream, there are no rules..."
Quote this message in a reply
Apprentice
Posts: 13
Joined: 2006.09
Post: #4
Taxxodium Wrote:That's a memory leak, you don't need to retain now, since you are only using it in this method.

Instead do this:

NSDate *now = [NSDate date];
[last release];
last = [now retain];

Thanks for the help Taxx. I am seeing now that memory management in Cocoa can be tedious.

Gerry

Quote:There is a name for people who are not excited about their work--unemployed.
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #5
Taxxodium Wrote:That's a memory leak, you don't need to retain now, since you are only using it in this method.

Instead do this:

NSDate *now = [NSDate date];
[last release];
last = [now retain];
Wait, you said you don't need to retain it, but you retain it there? Huh Regardless, it should be on the autorelease pool, so it would need to be retained if you want to keep it around. Retaining it where he did makes no difference from where you're retaining it. However, there will be a memory leak since there's no dealloc method releasing last.

Anyways, it looks like your problem is you need to retain last in your init method, since, as I said, it's on the autorelease pool. When doing memory management in Cocoa, I usually try to keep it as close to how I do it in C or C++ as possible. Here's the tricks I use:
If I use an object created without alloc, and I only use it one time, I just leave it be.
If I'm passing an object into a method for another object that's supposed to store its own copy (such as passing in a string for a file name), I just copy it rather than dealing with retaining.
If I insert an object into an array or other Cocoa data structure that's only meant to go in that data structure, I immediately release it. (since it retains the object)
Otherwise, I never call retain, and I release only if the object is destroyed or is replaced by another object.
Quote this message in a reply
Apprentice
Posts: 13
Joined: 2006.09
Post: #6
akb825 Wrote:Anyways, it looks like your problem is you need to retain last in your init method, since, as I said, it's on the autorelease pool. When doing memory management in Cocoa, I usually try to keep it as close to how I do it in C or C++ as possible. Here's the tricks I use:
If I use an object created without alloc, and I only use it one time, I just leave it be.
If I'm passing an object into a method for another object that's supposed to store its own copy (such as passing in a string for a file name), I just copy it rather than dealing with retaining.
If I insert an object into an array or other Cocoa data structure that's only meant to go in that data structure, I immediately release it. (since it retains the object)
Otherwise, I never call retain, and I release only if the object is destroyed or is replaced by another object.

Sounds like some good rules to go by. I may try those approaches but I really need to get a better understanding on Cocoa memory management. So I will probably spend some time reading through that information.

On another note. I have an application up and running that I need to get some opinions on. I know people don't really trust files that are linked here but I hope that some one will download and give me some feedback. I will be posting about it in another thread, because I have some questions/issues I need to get worked out that I will include in the post.

Gerry

Quote:There is a name for people who are not excited about their work--unemployed.
Quote this message in a reply
Moderator
Posts: 508
Joined: 2002.09
Post: #7
akb825 Wrote:However, there will be a memory leak since there's no dealloc method releasing last.

Code:
NSDate *now = [NSDate date];
[last release]; //<--------
last = [now retain];

Wink

"When you dream, there are no rules..."
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #8
Taxxodium Wrote:That's a memory leak, you don't need to retain now, since you are only using it in this method.

Instead do this:

NSDate *now = [NSDate date]; //<-- object B: retain count 0 (autoreleased)
[last release]; //<-- object A: retain count 0
last = [now retain];//<-- object B: retain count 1

I don't know what you're smiling about.
Your code has NO FUNCTIONAL DIFFERENCE than the code you quoted.

Xeno Wrote:NSDate *now = [[NSDate date] retain]; //<-- object B: retain count 1 (autoreleased + retain)
[last release]; //<-- object A: retain count 0
last = now; //<-- object B: retain count still 1
This is fine (and equivalent!). There's no memory leak inherent to this code.

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #9
Xenos Wrote:In Interface
Code:
{
    NSDate *last;
}

In Implementation
Code:
- (id) initMyObject
{
    if (self = [super init])
    {
        last = [[NSDate date] addTimeInterval: -1.0]; [b]//<-- problem is here[/b]
        return self;
    }
    else
        NSLog(@"*** self was not properly inited!");
}

- (void) doWork
{
    NSDate *now = [NSDate date];

    NSLog(@"** OUTSIDE TimeIntervalSinceNow: %0.1f",
        fabs([last timeIntervalSinceDate:now]));

    if (fabs([last timeIntervalSinceDate:now]) >= 1.0)
    {
        NSLog(@"** TimeIntervalSinceNow: %0.1f",
            fabs([last timeIntervalSinceDate:now]));

        // doMoreWork
        last = now;
    }
}

Console Output
Code:
[Session started at 2006-09-09 12:42:16 -0500.]
2006-09-09 12:42:17.030 myWorkApp[13970] ** OUTSIDE TimeIntervalSinceNow: 1.1

myWorkApp has exited with status -1.

Ok, so for some reason my app just exits at that point and one of two things happens:

1. After a clean build it stops on "class_initialize".
2. After being ran once and then ran again, without a clean build, it stops on "obj_msgSend".

Any ideas on what I am doing wrong here. Any tips that someone can provide that will help me troubleshoot the issue and possibly find it myself?

All help is appreciated. Thanks.

EDIT:
I believe I solved it. By doing the following it started working. If I am missing something else please let me know.

In initMyObject
last = [[[NSDate date] addTimeInterval: -1.0] retain];

and:

In doWork
NSDate *now = [[NSDate date] retain];
[last release];
last = now;

The problem is that your last variable holds an autoreleased value. Whenever you get an NSObject out of a method that is not -alloc...; or -new;, the standard behavior is to get an autoreleased object. This means if you want to keep it, you have to retain it. That means you're getting autoreleased objects out of -addTimeInterval; and +date;.

The basic assumption in Cocoa is that if you didn't -alloc; or -new; it, then it is autoreleased when you get it. Whether or not does in fact have a retain count of 0, doesn't matter. If you want to stick it in a semi-permanent variable like last, you're going to have to retain it. (in your original code you missed the first retain and you lose the object when the autorelease pool dumps it).

EDIT: Yes, your fix is the correct solution.
Might I add though that you neglect to clean up in a destructor.
Since you retain the NSDate in this object you should release it when your Object deallocs.
Code:
-(void)dealloc; {
    if (last != nil) [last release];
}

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
Apprentice
Posts: 13
Joined: 2006.09
Post: #10
Ok, these last few posts brought a question to me about retain and release.

Looking at just this portion of the code and assuming everything else has been initialized correctly.

Code:
- (void) doWork
{
    [b]// If I retain "now" here, I may run doWork several times
    // before I get to "[last release];" below, which is really
    // releasing a previously retained "now" object.[/b]
    NSDate *now = [[NSDate date] retain];

    NSLog(@"** OUTSIDE TimeIntervalSinceNow: %0.1f",
        fabs([last timeIntervalSinceDate:now]));

    if (fabs([last timeIntervalSinceDate:now]) >= 1.0)
    {
        NSLog(@"** TimeIntervalSinceNow: %0.1f",
            fabs([last timeIntervalSinceDate:now]));

        // doMoreWork
        [last release];
        last = now;
    }
}

Will that cause the retain count of "now" to continue rising or are they all autoreleased when I leave "doWork"?

Gerry

Quote:There is a name for people who are not excited about their work--unemployed.
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #11
Code:
- (void) doWork
{
    NSDate *now = [[NSDate date] retain];      //<-- object B
    // object B will always be a new NSDate instance in this method.
    // it's never the same one, and if you retain it without referencing it, you are leaking.

    NSLog(@"** OUTSIDE TimeIntervalSinceNow: %0.1f",
        fabs([last timeIntervalSinceDate:now]));

    if (fabs([last timeIntervalSinceDate:now]) >= 1.0)
    {
        NSLog(@"** TimeIntervalSinceNow: %0.1f",
            fabs([last timeIntervalSinceDate:now]));

        // doMoreWork
        [last release];    //<-- object A
        last = now;     //<-- object B
    }
    //<-- you forget that there's an implied else here.
    // if the above if statement evaluates to false, you're leaking object B.
    // when this method returns, if you haven't set last to now, you won't have a reference to now no matter what and it just sit in memory.

}

The best thing to do is, if you retain an object, immediately set an instance variable to reference it. Don't forget though, that whatever was in that instance variable should be released first if it references something you retained earlier.

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
Apprentice
Posts: 13
Joined: 2006.09
Post: #12
kelvin Wrote:The best thing to do is, if you retain an object, immediately set an instance variable to reference it. Don't forget though, that whatever was in that instance variable should be released first if it references something you retained earlier.

Thanks for adding that extra explanation. It really helps me understand. Incidentally, I ran this some portion of code with NSLog statements displaying the retainCount for "now" as the application was running. I could see that there was always one extra count for it when I retained outside of the if statement and did not release every time. So that gave me kind of a visual of what was going on.

Gerry

Quote:There is a name for people who are not excited about their work--unemployed.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Effects of improper/different namespace usage. Jones 8 3,595 Sep 18, 2006 06:40 AM
Last Post: TomorrowPlusX
  In-Game Memory Usage Info Griggs 1 3,163 Sep 5, 2002 09:12 PM
Last Post: zzajin