Optimise slow code - enumeration of dictionary - iphone

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

Related

random BAD ACCESS when saving user defaults

I'm simply trying to save this array to the user defaults and it will crash at random. Sometimes it works, sometimes it gives me the EXC_BAD_ACCESS. Am I not releasing something properly?
- (void)setTextValue:(NSString *)valueText indexToSet:(NSUInteger)index
{
[self.pageData replaceObjectAtIndex:index withObject:valueText];
[[NSUserDefaults standardUserDefaults] setObject:self.pageData forKey:#"mynotes"];
}
Here is the method that i've determined is causing the errors. It was a method already created by Xcode that I added my own custom code to.
- (nbookDataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
nbookDataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:#"nbookDataViewController"];
if (self.pageData.count > 0 && index < self.pageData.count)
{
NSString *val = (NSString *)[self.pageData objectAtIndex:index];
dataViewController.dataObject = val;
}
else
{
NSDate *date = [NSDate date];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MMMM d, YYYY"];
NSString *dateString = [dateFormat stringFromDate:date];
//[dateFormat release];
[self.pageData addObject:dateString];
dataViewController.dataObject = (NSString *)[self.pageData objectAtIndex:index];
}
dataViewController.myModel = (nbookModelController *)self;
dataViewController.dIndex = index;
//[self.mySaveData setObject:self.pageData forKey:#"mynotes"];
return dataViewController;
}
This tip will allow you code to break on the exception and let you check directly why this is happening:
https://stackoverflow.com/a/616526/46970

iphone how to pass string to another view label?

My problem is how to pass string to another view label? I got try so many example but still can not get the value where I pass.
here is I save the data.
-(IBAction)Save:(id)sender{
timedata = [datePicker date];
NSLog(#"timedata save is = %#",timedata);
time = [NSString stringWithFormat:#"%#",timedata];
NSLog(#"String time = %#",time);
[self dismissModalViewControllerAnimated:YES];
}
here is I want to show the save data.
- (void) viewWillAppear:(BOOL)animated {
show = [[SelectDate alloc]initWithNibName:#"SelectDate" bundle:nil];
show.time = time;
NSLog(#"time = %#",time);
Selectime.text = show.time;
NSLog(#"show.time = %#",show.time);
[super viewWillAppear:animated];
}
If you have set property for time in SelectDate viewController so that it can be accessed in other viewControllers.
//SelectDate.h
NSString *time;
// Your declarations
#property(nonatomic,retain) NSString *time;
//SelectDate.m
#synthesize time;
Now you can use time in other ViewControllers like you are doing.
To get a NSString representation of a date you should look at NSDateFormatter
example:
NSDate* timedata = [datePicker date];
NSDateFormatter* dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"dd/MM/yyyy"];
NSString *time = [dateFormat timedata];
[dateFormat release];
Instead of accessing last view, pass your data to your next view:
Hi,My problem is how to pass string to another view label? I got try so many example but still can not get the value where I pass.
here is I save the data.
-(IBAction)Save:(id)sender{
timedata = [datePicker date];
NSLog(#"timedata save is = %#",timedata);
time = [NSString stringWithFormat:#"%#",timedata];
NSLog(#"String time = %#",time);
[self dismissModalViewControllerAnimated:YES];
YourNextView *yourNextView=[[YourNextView alloc] init];
yourNextView.anString=[NSString stringWithFormat:#"%#",timedata];
[yourNextView release];
}
here is You want to show the save data.
- (void) viewWillAppear:(BOOL)animated {
//show = [[SelectDate alloc]initWithNibName:#"SelectDate" bundle:nil];
//show.time = time;
//NSLog(#"time = %#",time);
Selectime.text = self.anString;
NSLog(#"show.time = %#",show.time);
[super viewWillAppear:animated];
}

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.

iPhone PList and NSDate issues

I am doing:
NSString *path = [[self class] pathForDocumentWithName:#"Alarms.plist"];
NSArray *alarmDicts = [NSMutableArray arrayWithContentsOfFile:path];
if (alarmDicts == nil)
{
NSLog(#"MER. Unable to read plist file: %#", path);
path = [[NSBundle mainBundle] pathForResource:#"Alarms"
ofType:#"plist"];
alarmDicts = [NSMutableArray arrayWithContentsOfFile:path];
}
_displayedObjects = [[NSMutableArray alloc]
initWithCapacity:[alarmDicts count]];
for (NSDictionary *currDict in alarmDicts)
{
Alarm *alarm = [[Alarm alloc] initWithDictionary:currDict];
[_displayedObjects addObject:alarm];
}
pathForDocumentWithName just a helper method, assume it works (it does). I add all of the values of the plist to an object, and store it in an array. Now if I do something like this:
NSUInteger index = [indexPath row];
id alarm = [[self displayedObjects] objectAtIndex:index];
NSString *title = [alarm title];
[[cell detailTextLabel] setText:title];
It works perfectly fine. But when trying to format the NSDate type in the plist file (listed as 'datetime')
NSDate *datetime = [alarm datetime];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"hh:mm"];
[[cell textLabel] setText:[formatter stringFromDate:datetime]];
It throws the NSLog for alarmDicts being nil, and returns nil for the string. I'm out of ideas, and have been trying for a few hours to solve this. Anyone have any ideas?
Also, if I print out the description for datetime, it works perfectly. Only nils and errors out when I attempt to use the NSDateFormatter on it.
A massive guess, but are you sure the dates in your property list are being read in as NSDate objects? If I were you, I'd check the type of your apparent NSDate objects, e.g.
NSLog(#"%#", [[alarm datetime] class]);
I would be suspicious that they're being loaded as NSStrings, which NSDateFormatter will decline to process — but they'll still appear to log correctly.
Unrelated comment: I'm sure it's a copy and paste error only, but you're leaking 'Alarm' objects at the bottom of your first snippet of code.

preparing a core data result set for a grouped uitableview

i've got a NSMutableArray created from a data source object
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[self setAmountArray: mutableFetchResults];
every object in my mutable array has the two instance variables name and timeadded.
now i want to display all names in a uitableview grouped by the day they are added. for that i wrote the following method
-(NSMutableArray*)arrangeTransfersByDate:(NSMutableArray*)transferArray {
// Setting up objects for this method
NSDate *oldDate = [NSDate dateWithTimeIntervalSince1970:0.0f];
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"yyyy-MM-dd"];
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for(transfers *transfer in transferArray) {
if( [[dateComparisonFormatter stringFromDate:[transfer timeadded]] isEqualToString:[dateComparisonFormatter stringFromDate:oldDate]] ) {
if([returnArray count] == 0) {
[returnArray addObject:[NSMutableArray arrayWithObject:transfer]];
} else {
[[returnArray objectAtIndex:[returnArray count]-1] addObject:transfer];
}
} else {
[returnArray addObject:[NSMutableArray arrayWithObject:transfer]];
oldDate = [transfer timeadded];
}
}
//[returnArray release];
[dateComparisonFormatter release];
return returnArray;
}
transferArray is my amountArray where my core data objects are stored.
so this works! but
is there a better way to do this? can you give me something like a "best practise" or simply have a look if there are some memory leaks?
thanks!
edit:
the right answer was NSFetchedResultController and its sectionNameKeyPath.
however i don't wanted to store my data twice a time.
so i created the following getter method in my NSManagedObject.
- (NSString *) pubDate {
[self willAccessValueForKey:#"pubDate"];
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"dd.MM.YYYY"];
NSString *temp = [dateComparisonFormatter stringFromDate:[self pubTime]];
[dateComparisonFormatter release];
[self didAccessValueForKey:#"pubDate"];
return temp;
}
with this i can sort my tableviewcontroller by date using my FetchedResultController and my pubTime which is a timestamp.
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[self managedObjectContext]
sectionNameKeyPath:#"pubDate"
cacheName:#"transfersRoot"];
thanks to all
Generally when you want to display the results of a Core Data fetch in a UITableView, you use an NSFetchedResultsController. You can choose the attribute by which you want to group your results by specifying a sectionNameKeyPath during initialization.
That said, provided your method works then really it's up to you whether you want to change your code around or not.
But please make sure you [returnArray autorelease] before you return it. It's a generally accepted Objective-C practice that any method without "alloc", "new", or "copy" in the name will return an autoreleased object.