Diagnosing an autorelease error (EXC_BAD_ACCESS) - iphone

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;
}

Related

Objective C: Troubles archiving an NSDate

I'm attempting to save an altered NSDate (8am of the start date) in a database to be retrieved anytime the program is run. I am using object archiving. I thought I had the right code, but I can't seem to get it to save. I receive no errors, just the output I've put into my code. I know the date and time is correct because they are seen as output in NSLog. Here is my code:
__dataArea = [NSMutableData data];
__unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:__dataArea];
__archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:__dataArea];
__iDates = [[BCimportantDates alloc] initWithCoder:[NSKeyedUnarchiver unarchiveObjectWithFile: #"firstDate.arch"]];
if ((__iDates.firstDate == nil)){
NSDate *date = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
NSDateComponents *components = [gregorian components: NSUIntegerMax fromDate: date];
NSLog(#"the date %#",date);
[components setHour: 3];
[components setMinute: 00];
[components setSecond: 00];
__newDate = [gregorian dateFromComponents: components];
[__iDates setFirstDate: __newDate];
NSLog(#"%#",__iDates.firstDate);
[__iDates encodeWithCoder: __archiver];
[__archiver finishEncoding];
if ([__dataArea writeToFile:#"firstDate.arch" atomically:YES] == NO){
NSLog(#"archiving failed. ");
}
}
And here is the implementation of the encoder and decoder functions within BCimportantDates.m:
- (void) encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject: __firstDate forKey: kfirstDateKey];
}
- (id) initWithCoder: (NSCoder *) decoder{
if (self = [super init]) {
self.firstDate = [decoder decodeObjectForKey:kfirstDateKey];
}
return self;
}
I've tried using breakpoints where __iDates is encoded, where the archiver finishes, and where I check if it worked. The debugging was not that revealing, but to be honest I'm not sure what to look for when it comes to finding this kind of error. What else can I do to figure out this problem? What might be some possible solutions?
I think the problem here is that you are not specifying a path for writeToFile: to write to.
Try this:
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:#"firstDate.arch"];
if ([__dataArea writeToFile:path atomically:YES] == NO){
NSLog(#"archiving failed. ");
}
Which will write your file to a temporary directory, you can simply set a breakpoint or log the path variable to find out where this location is. NSTemporaryDirectory() is simply for an example though, as this folder is only temporary and can be deleted by the system at any time. Here is a category on NSFileManager which may provide you with a more appropriate path.

Fetch all events from EventStore EventKit iOS

i would like to know how to fetch all events from an EventStore using EventKit in iOS.
This way i can specify all events for today:
- (NSArray *)fetchEventsForToday {
NSDate *startDate = [NSDate date];
// endDate is 1 day = 60*60*24 seconds = 86400 seconds from startDate
NSDate *endDate = [NSDate dateWithTimeIntervalSinceNow:86400];
// Create the predicate. Pass it the default calendar.
NSArray *calendarArray = [NSArray arrayWithObject:defaultCalendar];
NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:calendarArray];
// Fetch all events that match the predicate.
NSArray *events = [self.eventStore eventsMatchingPredicate:predicate];
return events;
}
The correct should use a NSPredicate, which is created with:
NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:calendarArray];
I have tried using
distantPast
distantFuture
as startDate and endDate, no good.
So other A's from other Q's are not exaclty wha im looking for.
Thank you!
EDIT
I have tested and got to the conclusion that i can only fetch events in a period of 4 years maximum. Any way of getting past this? Without using multiple fetches..
Code for fetch all events into array :
NSDate *start = ...
NSDate *finish = ...
// use Dictionary for remove duplicates produced by events covered more one year segment
NSMutableDictionary *eventsDict = [NSMutableDictionary dictionaryWithCapacity:1024];
NSDate* currentStart = [NSDate dateWithTimeInterval:0 sinceDate:start];
int seconds_in_year = 60*60*24*365;
// enumerate events by one year segment because iOS do not support predicate longer than 4 year !
while ([currentStart compare:finish] == NSOrderedAscending) {
NSDate* currentFinish = [NSDate dateWithTimeInterval:seconds_in_year sinceDate:currentStart];
if ([currentFinish compare:finish] == NSOrderedDescending) {
currentFinish = [NSDate dateWithTimeInterval:0 sinceDate:finish];
}
NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:currentStart endDate:currentFinish calendars:nil];
[eventStore enumerateEventsMatchingPredicate:predicate
usingBlock:^(EKEvent *event, BOOL *stop) {
if (event) {
[eventsDict setObject:event forKey:event.eventIdentifier];
}
}];
currentStart = [NSDate dateWithTimeInterval:(seconds_in_year + 1) sinceDate:currentStart];
}
NSArray *events = [eventsDict allValues];
This is the method I am using in my app to fetch them.
NSDate *startDate = [NSDate distantPast];
NSDate *endDate = [NSDate distantFuture];
This is code in production
const double secondsInAYear = (60.0*60.0*24.0)*365.0;
NSPredicate* predicate = [eventStore predicateForEventsWithStartDate:[NSDate dateWithTimeIntervalSinceNow:-secondsInAYear] endDate:[NSDate dateWithTimeIntervalSinceNow:secondsInAYear] calendars:nil];
For you, I would recommend looking back and forward ten years.

Optimise slow code - enumeration of dictionary

I have the following code that decodes a JSON string into an array of objects that I can then use in a UITableView.
At first I thought the JSON decoding was the slow part but it appears that it is not as the "Dictionary Done" appears almost immediately.
Any ideas on how to get that code to be a little faster?
-(void)parseJSON:(NSString *)jsonData{
NSLog(#"Start parsing");
NSDictionary *deserializedData = [jsonData objectFromJSONString];
NSLog(#"Dictionary Done");
NSArray *flights = [deserializedData valueForKeyPath:#"flights.flight"];
NSMutableArray *localArray = [[NSMutableArray alloc] init ];
NSString *lastFlightno =#"";
for (NSDictionary *flight in flights){
ArchiveFlight *aFlight = [[ArchiveFlight alloc] initWithFlightno:[flight objectForKey:#"flightno"] route:[flight objectForKey:#"route"]];
aFlight.flightID = [flight objectForKey:#"primary_key"];
aFlight.timeStamp = [aFlight niceDate:[flight objectForKey:#"timestamp"]];
if (![lastFlightno isEqualToString:aFlight.flightno]) {
[localArray addObject:aFlight];
}
lastFlightno =aFlight.flightno;
[aFlight release];
}
NSLog(#"End Parsing");
[self loadupTable:localArray];
self.flightArray = localArray;
[localArray release];
}
EDIT: Added timestamps
Timestamps of the NSLogs as follows...
2011-04-26 13:22:36.104 App[1778:707] Finished request
2011-04-26 13:22:36.109 App[1778:707] Start parsing
2011-04-26 13:22:36.128 App[1778:707] Dictionary Done
2011-04-26 13:22:37.713 App[1778:707] End Parsing
Sample of the JSON...
{"flights":[{"flight":{"flightno":"RYR54WP","timestamp":"2011-04-26 12:13:04","route":"EGNX-LEAL","primary_key":"836453"}},{"flight":{"flightno":"RYR24LU","timestamp":"2011-04-26 09:14:03","route":"EVRA-EGNX","primary_key":"831318"}},{"flight":{"flightno":"RYR39WH","timestamp":"2011-04-26 05:33:03","route":"EGNX-EVRA","primary_key":"825492"}},{"flight":{"flightno":"RYR7PX","timestamp":"2011-04-25 20:07:03","route":"LELC-EGNX","primary_key":"816703"}},{"flight":{"flightno":"RYR2VB","timestamp":"2011-04-25 16:57:06","route":"EGNX-LELC","primary_key":"810900"}},{"flight":{"flightno":"RYR3JN","timestamp":"2011-04-25 12:36:04","route":"GCTS-EGNX","primary_key":"802631"}},{"flight":{"flightno":"RYR8GV","timestamp":"2011-04-25 06:07:03","route":"EGNX-GCTS","primary_key":"792945"}},{"flight":{"flightno":"RYR82QR","timestamp":"2011-04-24 19:42:04","route":"EPKK-EGNX","primary_key":"783306"}},{"flight":{"flightno":"RYR51PV","timestamp":"2011-04-24 16:31:05","route":"EGNX-EPKK","primary_key":"777835"}},{"flight":{"flightno":"RYR53AQ","timestamp":"2011-04-24 14:09:05","route":"LIME-EGNX","primary_key":"773572"}},{"flight":{"flightno":"RYR1CX","timestamp":"2011-04-24 11:02:05","route":"EGNX-LIME","primary_key":"768285"}},{"flight":{"flightno":"RYR9ZW","timestamp":"2011-04-24 08:21:04","route":"LEGE-EGNX","primary_key":"764624"}},{"flight":{"flightno":"RYR63BC","timestamp":"2011-04-24 05:48:02","route":"EGNX-LEGE","primary_key":"761726"}},{"flight":{"flightno":"RYR7PX","timestamp":"2011-04-23 19:39:03"
Formatted sample:
{
"flights":[
{
"flight":{
"flightno":"RYR54WP",
"timestamp":"2011-04-26 12:13:04",
"route":"EGNX-LEAL",
"primary_key":"836453"
}
},
{
"flight":{
"flightno":"RYR24LU",
"timestamp":"2011-04-26 09:14:03",
"route":"EVRA-EGNX",
"primary_key":"831318"
}
}
]
}
EDIT 2:
So here is "niceDate" that is causing the slowdown!
-(NSString *)niceDate:(NSString *)oldDate{
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init]autorelease];
[formatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *sourceDate = [formatter dateFromString:oldDate];
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateStyle:NSDateFormatterFullStyle];
[dateFormatter setTimeStyle:NSDateFormatterLongStyle];
NSString *timeString = [dateFormatter stringFromDate:sourceDate];
return [NSString stringWithFormat:#"%#",timeString];
}
Some things that come to mind:
NSArray *flights = [deserializedData valueForKeyPath:#"flights.flight"];
Do you need to use KVC? What is the structure of your JSON data?
ArchiveFlight *aFlight = [[ArchiveFlight alloc] initWithFlightno:[flight objectForKey:#"flightno"] route:[flight objectForKey:#"route"]];
aFlight.flightID = [flight objectForKey:#"primary_key"];
aFlight.timeStamp = [aFlight niceDate:[flight objectForKey:#"timestamp"]];
You always create an instance of ArchiveFlight and parse the timestamp…
if (![lastFlightno isEqualToString:aFlight.flightno]) {
[localArray addObject:aFlight];
}
…even though you don’t have to do that all the time. Depending on how many repeated flightnos you have, this can make a noticeable difference.
Why not read [flight objectForKey:#"flightno"], compare it to lastFlightno and, if and only if they’re different, create an instance, add it to the array, and release it?
Edit: Try the following KVC-free code:
NSArray *flights = [deserializedData objectForKey:#"flights"];
NSMutableArray *localArray = [[NSMutableArray alloc] init ];
NSString *lastFlightno =#"";
for (NSDictionary *flightWrapper in flights) {
NSDictionary *flight = [flightWrapper objectForKey:#"flight"];
NSString *flightno = [flight objectForKey:#"flightno"];
if (! [flightno isEqual:lastFlightno]) {
// create instance, add it to the array, release the instance
}
}
Edit: You’re creating and (auto)releasing two instances of NSDateFormatter inside this method. In general this would be okay, but since it is being executed >1K times there are two considerations: a) you’re creating/using/releasing those two instances >1K times when, in fact, you don’t have two, b) you should use an autorelease pool in your loop.
You should make this method a class method (or a function) since it doesn’t depend on the state of an instance of that class. Your formatters would be class (static) variables. For instance:
#implementation ArchiveFlight
static NSDateFormatter *formatter1; // choose better names!
static NSDateFormatter *formatter2;
+ (void)initialize {
if (self == [ArchiveFlight class]) {
formatter1 = [[NSDateFormatter alloc] init];
[formatter1 setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
formatter2 = [[NSDateFormatter alloc] init];
[formatter2 setDateStyle:NSDateFormatterFullStyle];
[formatter2 setTimeStyle:NSDateFormatterLongStyle];
}
}
+ (NSString *)niceDate:(NSString *)oldDate {
NSDate *sourceDate = [formatter1 dateFromString:oldDate];
NSString *timeString = [formatter2 stringFromDate:sourceDate];
return timeString;
// why +stringWithFormat:? It’s not necessary!
// return [NSString stringWithFormat:#"%#",timeString];
}
This fixes item a) but you really should use an autorelease pool inside your loop because the Cocoa methods you’re using return autoreleased objects. By using an autorelease pool for each iteration of your loop, you reduce the memory footprint of your code — although this could decrease performance as well. I suggest you try both with and without an inner autorelease pool.
for (NSDictionary *flightWrapper in flights) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
…
[pool drain];
}

Memory problem in NSXMLParser (iPhone)

Hi I'm trying to parse an xml and use the currentElementValue inside a code to get an expiredate. This is the code.
if([elementName isEqualToString:#"utlop"]) {
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterShortStyle];
int numberOfDays = [currentElementValue intValue];
NSDate *expireDate = [now addTimeInterval:60*60*24*numberOfDays];
NSString *expireString = [dateFormat stringFromDate:expireDate];
NSLog(#"ExpiryString :%#", expireString);
//Add values to Vare
enVare.utlop = expireString;
enVare.enhet = enhet;
enVare.isDirty = NO;
//Add Vare
[appDelegate addVare:enVare];
//Releasing
[dateFormat release];
[enVare release];
enVare = nil;
[currentElementValue release];
currentElementValue = nil;
[expireString release];
expireString = nil;
This results in a memory leak, but Im new to objective C so I can't find the error. When I just do this, it works:
enVare.utlop = currentElementValue;
Do not release objects that are not owned by you. You own an object when u create them using new or alloc. Release only those objects that are created by you using these functions. Also make sure that you release such objects once you have finished using them.

Date Sectioned table?

I have a plist which contains an array of NSDictionary 's which represent a certain event each dictionary contains a few pieces of information about the event and a NSDate with the date of the event e.g.
I wish to create a sectioned table view with this date very much like in the Calendar app which ships with the iPhone when you click on the "List" view. You can see there are only sections for the dates where there are events.
So whats the best way to find out to begin with how many NSDictionary 's have the same date (so I know how many sections to create and how many rows in each section, as each section will have a different amount or rows).
Thanks
I did something very similar to this for Reconnected except my sections are for years (see the History screenshot).
Sort the array by the date key.
Start with the first item. The date of the first item represents the first segment. In my case, I only care about the year.
Create an array for the section with the date. Add the currently inspected to the section's array. Add the section's array to another array that will be the array of all sections in your table.
Move on to the next item. If the next item's date equals the previous item's date, add the current item to the current section's array. Otherwise, apply step #3 to the new date.
Repeat the previous step for rest of the array from your plist.
At the end of step 5, you should have an array of sections. From that section you can send it a message for the number of NSDictionary's you've added to the section which will represent each rows in your table.
After a bit of playing around this is what I have come up with, at the moment its just a foundation tool to keep it clear.
#import <Foundation/Foundation.h>
NSDate* normalizedDateWithDate(NSDate *date) {
NSCalendar *calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *comp = [calendar components:unitFlags fromDate:date];
return [calendar dateFromComponents:comp];
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *plistPath = #"flights.plist";
NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];
NSMutableSet *flightDates = [[NSMutableSet alloc] init];
for (NSDictionary *oneFlight in array)
[flightDates addObject:normalizedDateWithDate([oneFlight objectForKey:#"flightDate"])];
NSLog(#"Number of Sections Required: %d", [flightDates count]);
NSMutableDictionary *datesAndFlights = [[NSMutableDictionary alloc] init];
for (NSDate *fDate in flightDates) {
NSMutableArray *sectionFlights = [[NSMutableArray alloc] init];
for (NSDictionary *oneFlight in array) {
if ([normalizedDateWithDate([oneFlight objectForKey:#"flightDate"]) isEqualToDate: normalizedDateWithDate(fDate)])
{
[sectionFlights addObject:oneFlight];
}
}
[datesAndFlights setObject:sectionFlights forKey:normalizedDateWithDate(fDate)];
[sectionFlights release];
}
NSEnumerator *enumerator = [datesAndFlights keyEnumerator];
NSDate *key;
while ((key = [enumerator nextObject])) {
NSLog(#"Key: %#", key);
for (NSDictionary *oneFlight in [datesAndFlights objectForKey:key]) {
NSLog(#"flightNumber: %# and Total Time: %#", [oneFlight objectForKey:#"flightNumber"], [oneFlight objectForKey:#"totalTime"]);
}
}
[array release];
[flightDates release];
[datesAndFlights release];
[pool drain];
return 0;
}
This is just what I have managed to put together and it seems to work but if anyone can see a way to make this better or more concise please say so! Also the function at the top which I use to make sure the date is always at time 00:00:00 when I compare it I have seen the NSCalendar – rangeOfUnit:startDate:interval:forDate: method in the documentation does anyone know if its better to use this instead?
Thanks