I am using a NSMutableArray to hold CLLocationCoordinate2D values. After testing for a few mins I found the app crashing. I found heavy memory leaks associated with both the array and CLLocationCoordinate2D values.
Here is the code:
NSMutableArray *arrayReturn = [[NSMutableArray alloc] init];
CLLocationCoordinate2D obj1 = CLLocationCoordinate2DMake(37.6085289,107.5941445);
CLLocationCoordinate2D obj2 = CLLocationCoordinate2DMake(27.1727738,78.041655);
[arrayReturn addObject:[NSValue valueWithBytes:&obj1 objCType:#encode(CLLocationCoordinate2D)]];
[arrayReturn addObject:[NSValue valueWithBytes:&obj2 objCType:#encode(CLLocationCoordinate2D)]];
return [arrayReturn autorelease];
I am creating several objects like this and adding it to the array. Even though I have tagged an autorelease at the end, I have heavy memory leaks in the array.
What am I doing wrong here?
A CLLocationCoordinate2D is a simple struct of 2 double values and not an NSObject!
You allocate the CLLocationCoordinate2D on the stack and pass pointers to that data in the NSValue objects. But the stack memory will be not available after the method returns. This is why your app crashes.
You need to convert the CLLocationCoordinate2D into NSObjects somehow.
For example like this:
NSArray* coords = [NSArray arrayWithObjects:[NSNumber numberWithDouble:obj1.latitude], [NSNumber numberWithDouble:obj1.longitude], nil];
Related
I've recently created a new class for my iPhone application which will hold information read from a text file containing the street address and GPS points of points of interest.
The issue though is that whenever I add code to initialize the class my application loads up and the instantly quits with no errors in the console. When I remove it, everything is fine. I simply cannot see anything wrong with the code.
Here is the constructor:
#import "GPSCoordinate.h"
#implementation GPSCoordinate
-(GPSCoordinate*) initWithData:(NSString *)rawData size:(int)size
{
self = [super init];
location = [NSMutableArray arrayWithCapacity:size];
coordinates = [NSMutableArray arrayWithCapacity:(int)size];
NSArray *tokens = [rawData componentsSeparatedByString:#"#"];
for (int i = 0; i < size - 1; i++) {
//Sub tokens
NSString *line = [tokens objectAtIndex:i];
NSArray *lineTokens = [line componentsSeparatedByString:#":"];
//Store address
[location addObject:[lineTokens objectAtIndex:0]];
//Store GPS coords
NSString *coords = [lineTokens objectAtIndex:1];
coords = [[coords stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:#""]
stringByReplacingCharactersInRange:NSMakeRange([coords length]-2, 1) withString:#""];
NSArray *coordsTokens = [coords componentsSeparatedByString:#" "];
CLLocationCoordinate2D coord;
coord.latitude = [[coordsTokens objectAtIndex:0] doubleValue];
coord.longitude =[[coordsTokens objectAtIndex:1] doubleValue];
[coordinates addObject:coords];
[line release];
[lineTokens release];
[coords release];
[coordsTokens release];
}
return self;
}
#end
Here is the call I make to it in another class:
self.gps = [[GPSCoordinate alloc] initWithData:gpsRawData size:[[gpsRawData componentsSeparatedByString:#"#"] count]];
Where am I going wrong with this?
I see a number of problems.
You're not checking the return value of [super init].
You're storing autoreleased arrays in what are presumably ivars (location and coordinates).
You're passing a separate size parameter which is calculated from the rawData outside of the call, but -initWithData: makes the exact same calculation inside the method. The size: parameter seems completely superfluous here.
You're skipping the last token entirely. You should take that for loop and make the condition simply i < size. Alternately if you're targetting iOS 4.0 or above you can turn the entire loop into
[tokens enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSString *line = obj;
// rest of loop body
}];
Since you don't seem to need the index inside the loop, you could also just use a for-in loop (this will work on pre-4.0 iOS devices):
for (NSString *line in tokens) {
// body of loop
}
You're not checking that your data is valid. If a line contains "foo", your program will crash when it tries to access [lineTokens objectAtIndex:1]. Similarly it'll crash if you have the string "foo:" as it tries to remove the first character of the coordinates variable. In fact anything less than 2 characters after the colon will crash. It'll also crash if there's no spaces after the colon.
And finally, all those calls to -release at the end will crash. All 4 of those objects are autoreleased objects, so by calling -release on them now you're simply guaranteeing that the app will crash when the autorelease pool is drained.
You're also storing coords (e.g. the string) in your coordinates array. Presumably you meant to store coord, though you'll need to wrap it in an NSValue in order to store it in an NSArray.
I see several issues.
1) Most fundamentally, you are releasing a lot of objects that you didn't allocate. For example:
NSString *line = [tokens objectAtIndex:i];
....
[line release];
is incorrect. Review the Cocoa Memory Management Rules.
2) Why are you doing [[gpsRawData componentsSeparatedByString:#"#"] count to pass the size to
your initWithData:size: method, when you're just going to have to repeat the -componentsSeparatedByString: call inside your method. Passing a separate "size" doesn't gain you anything, involves a redundant parse of the input, and opens up more possible bugs (what if the caller passes in a "size" that doesn't match the number of "#"s in the input - you aren't handling that error condition).
3) I also see that you are assigning latitude/longitude to CLLocationCoordinate2D coord; but not doing anything with it. Is that deliberate?
I've some memory issues with CLLocation.
CLLocation *annotation = [[CLLocation alloc] initWithLatitude:[[tempDict objectForKey:#"lat"] doubleValue] longitude:[[tempDict objectForKey:#"lon"]doubleValue]];
CLLocation *item2 = [[CLLocation alloc] initWithLatitude:[newLatString doubleValue] longitude:[newLongString doubleValue]];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%.1f km",[item2 distanceFromLocation:annotation]/1000];
[annotation release];
[item2 release];
So I tried to do this, but I realised that you can't set the annotation's coordinate.
CLLocationCoordinate2D tempCoordinate = annotation.coordinate;
tempCoordinate.latitude = [[tempDict objectForKey:#"lat"] doubleValue];
tempCoordinate.longitude = [[tempDict objectForKey:#"lon"] doubleValue];
annotation.coordinate = tempCoordinate;
Is there a workaround this? I don't want to be alloc/initing a CLLocation everytime cellForRowAtIndexPath is called..
your resultant object is an NSString - just create a class which contains an NSString, as well as references/ivars of the intermediate data where necessary. then using an observer idiom, just update the cells when the string changes (design it so the string depends on the coordinates). you could probably make a class which takes a set of arguments at initialization (e.g. coordinates), creates an NSString during initialization, and then refer to the result if your data never changes. it really depends on what data you expect will mutate, and at what frequency.
I don't want to be alloc/initing a
CLLocation everytime
cellForRowAtIndexPath is called..
Why not? Do you know it's causing performance problems? You're releasing them right away, so they aren't taking up extra memory. CLLocation looks like a pretty lightweight class, and the Objective-C runtime is heavily optimized, so they probably alloc / init pretty quickly. Until you see scrolling / perf / memory issue, I would go with what works and is easy to maintain.
Premature optimization is the root of all evil - Donald Knuth
NSArray *planetArray = [NSArray arrayWithObjects:#"Earth",
#"Jupiter",
#"Saturn",
#"Neptune",
#"Pluto", nil];
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
for(NSString *eachPlanet in planetArray) {
Planet *newPlanet = [[Planet alloc] init];
[newPlanet setValue:eachPlanet forKey:#"name"];
[newPlanet setValue:#"TEST" forKey:#"type"];
[newPlanet setValue:[NSNumber numberWithInt:1234] forKey:#"mass"];
[objectArray addObject:newPlanet];
[newPlanet release];
}
for(Planet *displayEachPlanet in objectArray) {
NSLog(#"DATA: %#", displayEachPlanet);
}
[objectArray release];
I am curious if this is the best way to create an object and set an iVar for each item in an array. Basically I am:
Creating a Planet object
Setting the iVar (from the NSString array)
Adding the Planet object to an array.
Releasing the Planet object
Printing my Planet objects
Releasing the array
NB: I am just testing, this is not for anything, I was just curious ...
cheers Gary
Can't see anything drastically wrong about doing it that way. One suggestion would be to have an extended initialiser for your planet class, along the lines of:
-(Planet*) initWithName:(NSString*)name andType:(NSString*)type withMass:(int)mass;
And then create the planet with:
Planet *newPlanet = [[Planet alloc] initWithName:eachPlanet andType:#"Test" withMass:42];
Looks good to me. If all you are doing with the objects is printing something from them, you could probably do it in one loop with less initializing and such, but if thats just a test..it looks fine.
I've looked through countless questions on here and elsewhere and cannot for the life of me figure out what I'm doing wrong.
I'm trying to store an array of CGPoints as NSValues inside of an NSMutableArray named points like so on the iPhone:
NSValue *point = [NSValue valueWithCGPoint:firstTouch];
NSLog(#"NSValue *point = %#", point);
[points addObject:point];
NSLOG OUTPUT
NSValue *point = NSPoint: {120, 221}
Everything is going smooth converting from the CGPoint to NSValue. But when I try to retrieve the point I get nothing.
NSValue *getPoint = [points objectAtIndex:0];
CGPoint thePoint = [getPoint CGPointValue];
NSLog(#"Point = %#", NSStringFromCGPoint(thePoint));
NSLOG OUTPUT
Point = {0, 0}
The points should be the same but I'm getting a null result.
For testing purposes this is happening in the touchesBegan method.
Does anyone have any idea where I'm going wrong? Thanks in advance.
I never allocated my array into memory and initialized it. My points weren't being stored into an array because there was no array that existed.
for adding CGPoint to NSMutableArray
[ArrObj addObject:NSStringFromCGPoint(PointObj)];
for getting CGPoint from NSMutableArray
CGPoint pointObj2 = CGPointFromString([ArrObj objectAtIndex:index]);
may this help you..
First allocate your mutable array in memory:
points = [[NSMutableArray alloc] init];
Then insert.
I'm new to Objective-C and iPhone development, and I'm trying to store floating-point values in an NSMutableArray, but when I do I get an error saying "incompatible type for argument 1 of 'addObject". What am I doing wrong? I'm trying to create an array of doubles that I can perform math calculations with.
NSMutableArray only holds objects, so you want an array to be loaded with NSNumber objects.
Create each NSNumber to hold your double then add it to your array. Perhaps something like this.
NSMutableArray *array = [[NSMutableArray alloc] init];
NSNumber *num = [NSNumber numberWithFloat:10.0f];
[array addObject:num];
Repeat as needed.
Use an NSNumber to wrap your float, because the dictionary needs an object:
[myDictionary setObject:[NSNumber numberWithFloat:0.2f] forKey:#"theFloat"];
/* or */
[myDictionary setObject:#0.2f forKey:#"theFloat"];
retrieve it by sending floatValue:
float theFloat = [[myDictionary objectForKey:#"theFloat"] floatValue];
Code is untested.
You can wrap many other data types in NSNumber too, check the documentation. There's also NSValue for some structures like NSPoint and NSRect.
In Cocoa, the NSMutableDictionary (and all the collections, really) require objects as values, so you can't simply pass any other data type. As both sjmulder and Ryan suggested, you can wrap your scalar values in instances of NSNumber (for number) and NSValue for other objects.
If you're representing a decimal number, for something like a price, I would suggest also looking at and using NSDecimalNumber. You can then avoid floating point inaccuracy issues, and you can generally use and store the "value" as an NSDecimalNumber instead of representing it with a primitive in code.
For example:
// somewhere
NSDecimalNumber* price = [[NSDecimalNumber decimalNumberWithString:#"3.50"] retain];
NSMutableArray* prices= [[NSMutableArray array] retain];
// ...
[prices addObject:price];
NSMutableArray *muArray = [[NSMutableArray alloc] init];
NSNumber *float = [NSNumber numberWithFloat:210.0f];
NSNumber *float1 = [NSNumber numberWithFloat:211.0f];
[muArray addObject:float];
[muArray addObject:float1];
NSlog(#"my array is--%#",muArray);