NSDate & Memory management - iphone

memory management still gives me grief. This time it is an NSDate iVar that I init using
NSDate *myNSDate = [[NSDate date] firstDayOfMonth];
with a method call to
- (NSDate *)firstDayOfMonth {
NSDateComponents *tmpDateComponents = [[NSCalendar currentCalendar]
components:NSYearCalendarUnit | NSMonthCalendarUnit | NSEraCalendarUnit | NSWeekCalendarUnit | NSWeekdayOrdinalCalendarUnit
fromDate:self];
[tmpDateComponents setDay:1];
[tmpDateComponents setHour:0];
[tmpDateComponents setMinute:0];
[tmpDateComponents setSecond:0];
return [[NSCalendar currentCalendar] dateFromComponents:tmpDateComponents];
}
At the end of the init call the retain count is at 1 (Note the iVar is not defined as a property).
When I step into the viewWillAppear method the myNSDate has vanished. I tried to do an explicit retain on it, but that only lasts until I update the iVar using the above method again.
I though - ok - I add the retain to the return of the function, but that makes the leak analyser throw up an error.
What am I doing wrong?

Your method firstDayOfMonth is correct as it is given in your question. However, the return value of that method is an autoreleased date, which means that if you don't retain the return value somewhere else, it will disappear.
So you need to do something like this (assuming your ivar is named firstDayOfMonth:
- (id) init {
if (self = [super init...]) {
...
[self setFirstDayOfMonth:[[NSDate date] firstDayOfMonth]];
}
return self;
}
- (void) setFirstDayOfMonth:(NSDate *)newFirstDay {
[firstDayOfMonth release];
firstDayOfMonth = [newFirstDay retain];
}
- (void) dealloc {
[firstDayOfMonth release];
...
[super dealloc];
}
In this, you're explicitly retaining the return value (but also making sure to release the old value so you're not leaking memory). Now your date object will live until you set a new date, or the object is deallocated and the date is destroyed in the dealloc method.

Related

Diagnosing an autorelease error (EXC_BAD_ACCESS)

I've been playing around with core data and started writing some methods to query different date ranges of data. My core data model is very simple (Entity named Smoke with one field - timestamp (of type date).
When I execute my code, the proper count gets returned, but I get an autorelease error - I used NSZombies to track it to the below method:
- (NSUInteger)retrieveSmokesForUnit:(NSCalendarUnit)unit
{
NSDate *beginDate = [[NSDate alloc] init];
NSDate *endDate = [[NSDate alloc] init];
[self rangeForUnit:unit containingDate:[NSDate date] startsAt:&beginDate andEndsAt:&endDate];
NSInteger count = [self numberOfSmokes:beginDate toDate:endDate];
[beginDate release];
[endDate release];
return count;
}
So I get the concept - I am releasing the NSDate objects beginDate and endDate too many times - but why does that happen? I thought the rule was when you instantiate with alloc, you use release? I don't release them explicitly anywhere else in the code, so there must be something going on behind the scenes. If someone could point me in the right direction, that would be great!
Here are the other methods involved, since the issue must be somewhere in these. I assume it has to do with how I'm passing pointers to the dates around?
The initial call, called in the view controller
- (IBAction)cigButtonPressed
{
NSUInteger smokes = [[DataManager sharedDataManager] retrieveSmokesForUnit:NSWeekCalendarUnit];
NSLog(#"Count test = %i", smokes);
}
This calles the method posted a the beginning of the question, which in turn calls:
- (NSUInteger)numberOfSmokes:(NSDate *)beginDate toDate:(NSDate *)endDate {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Smoke" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//Create predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(timeStamp >= %#) AND (timeStamp < %#)", beginDate, endDate];
//Setup request
[request setEntity:entity];
[request setPredicate:predicate];
NSError *error;
NSUInteger smokes = [self.managedObjectContext countForFetchRequest:request error:&error];
NSLog(#"Number of smokes retrieved: %d", smokes);
[request release];
return smokes;
}
Thanks!
Edit - left out a related method:
- (void)rangeForUnit:(NSCalendarUnit)unit containingDate:(NSDate *)currentDate startsAt:(NSDate **)startDate andEndsAt:(NSDate **)endDate {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar rangeOfUnit:unit startDate:&*startDate interval:0 forDate:currentDate];
*endDate = [calendar dateByAddingComponents:[self offsetComponentOfUnit:unit] toDate:*startDate options:0];
[calendar release];
}
In:
- (void)rangeForUnit:(NSCalendarUnit)unit containingDate:(NSDate *)currentDate startsAt:(NSDate **)startDate andEndsAt:(NSDate **)endDate {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar rangeOfUnit:unit startDate:&*startDate interval:0 forDate:currentDate];
*endDate = [calendar dateByAddingComponents:[self offsetComponentOfUnit:unit] toDate:*startDate options:0];
[calendar release];
}
startDate and endDate are output parameters. They are not owned by the caller, hence they should not be released.
Then, in:
- (NSUInteger)retrieveSmokesForUnit:(NSCalendarUnit)unit
{
NSDate *beginDate = [[NSDate alloc] init];
NSDate *endDate = [[NSDate alloc] init];
[self rangeForUnit:unit containingDate:[NSDate date] startsAt:&beginDate andEndsAt:&endDate];
NSInteger count = [self numberOfSmokes:beginDate toDate:endDate];
[beginDate release];
[endDate release];
return count;
}
the following happens:
You create a new NSDate object via +alloc, hence you own it. beginDate points to this new object;
You create a new NSDate object via +alloc, hence you own it. endDate points to this new object;
You send -rangeUnit:containingDate:startsAt:andEndsAt:, passing the address of beginDate and endDate as arguments. Upon return, these two variables point to whatever was placed in them by the method. You do not own the corresponding objects (see above), and you’ve leaked the two NSDate objects you created in steps 1 and 2.
You send -release to both beginDate and endDate. You don’t own them, hence you shouldn’t release them.
In summary:
You shouldn’t be creating new objects for beginDate and endDate since they’re being returned by -rangeUnit… This causes memory leaks;
You shouldn’t be releasing beginDate and endDate because you do not own the objects returned by -rangeUnit… This causes overreleases.
The following code should fix your leaks and overreleases:
- (NSUInteger)retrieveSmokesForUnit:(NSCalendarUnit)unit
{
NSDate *beginDate;
NSDate *endDate;
[self rangeForUnit:unit containingDate:[NSDate date] startsAt:&beginDate andEndsAt:&endDate];
NSInteger count = [self numberOfSmokes:beginDate toDate:endDate];
return count;
}

How do I measure the time interval from a starting point using NSDate?

I have one method that I use in many places throughout my project that looks like the following:
-(void)showSignInView
{
if(check for time interval)
[[self superview] addSubview:loginController.view];
}
I'd like to note the first time that this method is called, then on every subsequent call of this method check to make sure that the interval has been more than 15 minutes from the original call. Only then will it execute the rest of its code.
I know that you can use NSDate to measure time intervals using code like the following:
NSDate *firstTime = [[NSDate date] retain];
NSDate *SecondTime = [NSDate date];
NSLog(#"Time elapsed: %f", [SecondTime timeIntervalSinceDate:firstTime]);
but I'm not sure how to implement the initial time check, then subsequent comparisons to that time. How can I do this?
Create a property named previousTime.
#property(nonatomic, retain) NSDate *previousTime;
And create a method to find the time difference.
- (NSTimeInterval)timeDifferenceSinceLastOpen {
if (!previousTime) self.previousTime = [NSDate date];
NSDate *currentTime = [NSDate date];
NSTimeInterval timeDifference = [currentTime timeIntervalSinceDate:previousTime];
self.previousTime = currentTime;
return timeDifference;
}
You could use GCD to achieve this. The dispatch_once() function can arrange that a block is only executed once in the lifetime of your app.
NSDate *firstTime = nil;
- (void)loadView {
[self calculateTime:[NSDate dateWithTimeIntervalSince1970:1312996898]];
}
- (void)calculateTime:(NSDate*)secondTime
{
double offset = [secondTime timeIntervalSinceDate:[self getFirstTime]];
if (offset >= 900.0) {
NSLog(#"15 min gone");
}
}
- (NSDate *)getFirstTime
{
if (!firstTime) {
firstTime = [[NSDate date] retain];
}
return firstTime;
}

Objective C instance variable initialization in a method

Did any body get this issue?
If I need an instance variable, not as a property, and initialize this variable in a method, then when I need it, it is already released. It happens for autoreleased objects. What is the reason for this?
Usually instance variable should have the whole lifetime of the class object. But it seems if the variable is local to a function, and its a autorelease object, it is released when the function exits.
MyClass.h
#interface MyClass:UIViewController {
NSDate * date;
}
MyClass.m
#implementation MyClass {
- (void) anInit {
date = [NSDate date];
}
- (void) useDate {
NSLog (#"%#", date);
// here date is already release, and get bad access.
}
}
You need to retain date.
An autoreleased object will be released when the autorelease pool is next drained. When this happens has nothing to do with the lifecycle of your object.
Your implementation should look like this:
#implementation MyClass {
- (void) anInit {
date = [[NSDate date] retain]; // or [[NSDate alloc] init]
}
- (void) useDate {
NSLog (#"%#", date);
}
- (void) dealloc {
[date release];
[super dealloc];
}
}
[NSDate date] is a Convenience Constructor and is autoreleased, you need to add a retain call. Also make sure anInit is only called once or you will create a memory leak without calling [date release] first.
- (void) anInit {
date = [[NSDate date] retain];
}

NSDate timeInterval = nil

I am trying to compare two NSDates one is created by the viewDidLoad method and the other by clicking a button. I want to be able to click the button and find the time difference since the viewDidLoad was ran. I keep getting a difference of nil. Any Ideas?
#import "TimeViewController.h"
id startTime;
#implementation TimeViewController
- (void)viewDidLoad {
NSDate *startTime = [NSDate date];
NSLog(#"startTime = %#",startTime);
}
- (IBAction)buttonPressed{
NSDate *now = [NSDate date];
NSLog(#"now = %#",now);
double timeInterval = [now timeIntervalSinceDate:startTime];
NSLog(#"time difference = %#",[NSString stringWithFormat:#"%g",timeInterval]);
}
You have
id startTime;
in the global scope, and also
NSDate *startTime = [NSDate date];
inside viewDidLoad. The second statement creates a local variable called startTime, which hides the global variable. Use
startTime=[[NSDate date] retain];
instead.
That said, I'd suggest you not to create the global variable. Instead, make it an instance variable and a property:
#interface TimeViewController :NSObject{
....
NSDate*startDate;
}
...
#end
and as Kubi said, don't forget
-(void)dealloc{
[startDate release];
[super dealloc];
}
I'd also suggest not to use id to hold a known object. Who told you that? That's a very bad practice. Even when you declare a global variable, you should use
NSDate*startDate;
so that the compiler can warn you against non-defined methods.

NSDate assign problem

I would like to assign a date from one view controller to another
-(void) setCurrentDate:(NSDate newDate){
self.currentDate = newDate;
[self updateView];
}
While debugging I see the currentDate value out of scope and the application crashes with EXC_BAD_ACCESS.
Any help will be appreciated.
Besides that your setter should take NSDate by pointer (all class-type instances are passed by pointer in Objective-C), you are recursively calling the setter:
self.currentDate = foo results in [self setCurrentDate:foo] being called.
Correctly it should look e.g. like this (assuming a nonatomic, retain property):
- (void)setCurrentDate:(NSDate *)newDate {
if (currentDate != newDate) {
[currentDate release];
[newDate retain];
currentDate = newDate;
[self updateView];
}
}
Alternatively name that method different from the setter so you can use the synthesized setter:
- (void)updateDate:(NSDate *)newDate {
self.currentDate = newDate;
[self updateView];
}
You need to pass the pointer to date. Something like this:
-(void) setCurrentDate:(NSDate* newDate){
[self.currentDate release];
self.currentDate = newDate;
[self.currentDate retain];
[self updateView];
}
Of course, your currentDate class variable should also be a NSDate pointer. It will be even better if you use a property instead of a custom made setter.
Possibly, you need to retain newDate or copy it, if it's possible.
What I mean:
You create newDate
You call setCurrentDate
You release newDate
[self updateView] try to use it and fails because it is already released.
You also can try NSZombieEnabled to catch this kind of bugs.
In your method name, you use (NSDate date). You forgot to include the "*", which makes it a pointer. The correct code should be
-(void) setCurrentDate:(NSDate *newDate){ // Notice the star after NSDate
    self.currentDate = newDate;
    [self updateView];
}