I'm making an app that displays the user on the map along with multiple restaurant listings. When the user taps a pin, it stores the coordinates from the annotation and compares them against the users to ensure they are different. Once it is determined that they are different, it sends the coordinates of the business along with the coordinates of the user to Google to request directions. The code is working fine, but in order for it to do so I had to declare a few variables in ways that cause memory leaks. I'm hoping to clean the code up and learn where I erred and the proper way this should be handled.
So below is my code for getting the coordinates from the annotation that was tapped. If I attempt to initialize selectedAnnotation and allocate it's memory in say viewDidLoad by putting selectedAnnotation = [[MapLocation alloc] init]; Then it still shows up as a memory leak. And for reference, selectedAnnotation is a MapLocation(that conforms to MKAnnotation) variable, and as a property I have it (nonatomic, retain) and #synthesize(d).
I thought as long as I allocated it in memory, so long as I set its value to nil in viewDidUnload and released it in dealloc, that there should be no memory issues. What am I missing? Below is a screen shot of my memory leaks when I allocate the memory for selectedAnnotation in viewDidLoad along with the code provided below. If I have already allocated the memory, and check that the variable exists, why would it allocate the memory for the variable again? This happens for any restaurant pin I click on, but obviously not on the user's pin, because I have the code to release it in that case.
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
//NSLog(#"Selected annotation view");
// if we don't have the place holder already allocated
// lazy load the MapLocation placeholder variable
if(!selectedAnnotation)
{
selectedAnnotation = [[MapLocation alloc] init];
}
// save the annotation clicked
selectedAnnotation = view.annotation;
// if the annotation selected was is the same as the user's location
if((selectedAnnotation.coordinate.latitude == savedUserLocation.coordinate.latitude) && (selectedAnnotation.coordinate.longitude == savedUserLocation.coordinate.longitude))
{
// set it to nil and release it
selectedAnnotation = nil;
[selectedAnnotation release];
}
}
I'm having similar trouble with memory issues with the method below. I'm bringing in the JSON data from Google, to extract the address and coordinates of the user's location to display in the AnnotationView. I created all the necessary Arrays and Dictionaries to access the information, but once I allocate the memory for them and assign their values to savedUserLocation, if I try releasing say the NSDictionary variable userLocation, even as the last line of code in this method, the app crashes due to "[CFDictionary release]: message sent to deallocated instance 0x83ccb60". I'm pretty sure it's because I'm setting the values in savedUserLocation via pointer, and once the memory is released that information no longer exists, so what would be the proper way to allocate/release the memory to where I can access the information, without causing memory leaks? I've also tried using autorelease, but the same issues persist.
Here is the code that places the user pin.
- (void)fetchedData:(NSData *)responseData
{
//parse out the json data
NSError *error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSArray *results = [json objectForKey:#"results"]; //2
NSUInteger counter = [results count];
NSDictionary *userLocation = [[NSDictionary alloc] init];
//NSString *address = [[NSString alloc] init];
for(NSUInteger i=0; i < counter; i++)
{
userLocation = [results objectAtIndex:i];
// 2) Get the funded amount and loan amount
NSString *address = [[NSString alloc] initWithString:[userLocation objectForKey:#"formatted_address"]];
NSArray *types = [userLocation objectForKey:#"types"];
NSDictionary *geometry = [userLocation objectForKey:#"geometry"];
NSDictionary *location = [geometry objectForKey:#"location"];
float lat = [[location objectForKey:#"lat"] floatValue];
float lon = [[location objectForKey:#"lng"] floatValue];
CLLocationCoordinate2D newCoordinates;
newCoordinates.latitude = lat;
newCoordinates.longitude = lon;
// count how many types there are
NSUInteger numberOfTypes = [types count];
NSString *type = [[NSString alloc] init];
for(NSUInteger j=0; j < numberOfTypes; j++)
{
type = [types objectAtIndex:j];
if([type rangeOfString:#"street_address" options:NSCaseInsensitiveSearch].location != NSNotFound)
{
NSLog(#"%#", address);
if(!savedUserLocation)
{
savedUserLocation = [[MapLocation alloc] init];
}
[savedUserLocation setTitle:#"You are here!"];
[savedUserLocation setSubtitle:address];
[savedUserLocation setCoordinate:newCoordinates];
}
}
}
// determine which location is closest to the user by calling this function
MapLocation *closestLocation = [self determineClosestLocationToUser:allLocations locationOfUser:savedUserLocation];
// send in the user location and the closest store to them to determine appropriate zoom level and
// to center the map between the two
[self determineMapCenterAndZoomLevelFromUser:savedUserLocation andClosestLocation:closestLocation];
if(!pinDropped)
{
// add the annotation to the map and then release it
[mapView addAnnotation:savedUserLocation];
pinDropped = true;
}
}
Thanks for any and all help/suggestions/advice. I'd really like to understand the nuts and bolts of what I'm doing wrong, as I thought I had a pretty decent grasp on it.
In didSelectAnnotationView, you have this code:
selectedAnnotation = nil;
[selectedAnnotation release];
This causes a memory leak because you are setting selectedAnnotation to nil and then calling release on it.
The call to release does nothing because selectedAnnotation is nil at that point and a call to nil does nothing. This means the memory that had been allocated is never released but since the pointer variable has been set to nil, when didSelectAnnotationView is called again, your code allocates a new object.
You should switch the order of the two statements (call release first and then set to nil).
However, you don't need to alloc a new object just to keep a reference to the "selected annotation".
Declaring a regular ivar (not a retain property) and just setting it equal to the selected annotation should work.
In addition, the map view already has a property called selectedAnnotations which you should be able to use (so you don't need to maintain your own ivar or property). The map view's property is an NSArray but will always contain either 0 or 1 object(s). Be sure to check its count before accessing the object at index 0.
In fetchedData, you have several memory leaks caused by unnecessary alloc calls.
They are not needed because after calling alloc, you are then directly assigning a new reference to the pointer you just allocated memory for.
For example, userLocation is alloc'd before the for-loop but then inside the loop you directly point that variable to an object in the results array.
This means the memory that had originally been allocated for userLocation becomes abandoned with no reference to it. When you try to call release on userLocation, it is trying to release an object that was not allocated by code in fetchedData.
To fix at least userLocation, just declare the variable and don't alloc/init/release it.
The variables address and type (the NSString) have a similar issue.
Related
I tried looking over all these problems on StackOverflow and couldn't find anything that helped me. I can't even replicate it myself, i got this from actual users through iTunes Connect. This are the crash logs on Xcode:
This is my full serializedLocations method that is crashing, nothing stripped from it:
- (NSMutableArray *)serializedLocations:(NSArray *)locations withTimestamp:(NSInteger)timestamp{
NSMutableArray *serializedLocations = [NSMutableArray new];
if(locations){
for (CLLocation *location in locations) {
NSInteger locationTimeInterval = floor([location.timestamp timeIntervalSince1970] * 1000);
NSInteger t = locationTimeInterval - timestamp;
NSMutableDictionary *serializedLocation = [NSMutableDictionary new];
serializedLocation[#"x"] = [NSNumber numberWithDouble:location.coordinate.latitude];
serializedLocation[#"y"] = [NSNumber numberWithDouble:location.coordinate.longitude];
serializedLocation[#"a"] = [NSNumber numberWithDouble:location.horizontalAccuracy];
serializedLocation[#"v"] = [NSNumber numberWithDouble:location.speed];
serializedLocation[#"o"] = [NSNumber numberWithDouble:location.course];
serializedLocation[#"t"] = [NSNumber numberWithLong:t];
[serializedLocations addObject:serializedLocation];
}
}
return serializedLocations;
}
I can't seem to find the flaw.
I'm creating a temp new array.
I don't change the array that I'm enumerating.
I'm adding new objects to the temp array.
I'm returning that new temp array
EDIT:
The parent that gets all the other methods going is called like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ApiClient insertEvent:event withLocations:locations];
});
I'm guessing that locations, although typed here as an NSArray, is in fact an NSMutableArray — and that this is the array that is being mutated while you are enumerating it. If you are going to run this code on a background thread, you need to ensure that locations is not being mutated "behind your back". An easy way to do that would be to make a deep copy of locations at the call site and pass that rather than the mutable array. (Note that it is not sufficient to make the deep copy here in serializedLocations:withTimestamp:, as locations could be mutated during the copy.)
Point of my view, when we create object and then set it = nil, this object will be release. but I tried to insert a lot of data to NSMutableDictionary, and then i set NIL for each object. Memory still be there and did not decrease. Please see my code:
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary*frameAnotationLeft=[[NSMutableDictionary alloc] init];;
// Do any additional setup after loading the view, typically from a nib.
NSMutableDictionary * testDict = [[NSMutableDictionary alloc] init];
frameAnotationLeft = [[NSMutableDictionary alloc] init];
for ( int j=0; j<1000; j++) {
for (int i= 0; i<5000; i++) {
NSString* __weak test = #"dule.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.";
NSMutableArray * arrayTest = [[NSMutableArray alloc] init];
[arrayTest addObject:test];
test = nil;
[testDict setObject:arrayTest forKey:[NSString stringWithFormat:#"%d",i]];
arrayTest = nil;
}
[frameAnotationLeft setObject:testDict forKey:[NSString stringWithFormat:#"%d",j]];
}
testDict = nil;
// Begin to release memory
for (NSString* key in frameAnotationLeft) {
NSMutableDictionary *testDict2 = (NSMutableDictionary*)[frameAnotationLeft objectForKey:key];
for (NSString* key2 in testDict2) {
NSMutableArray * arrayTest = (NSMutableArray *)[testDict2 objectForKey:key2];
NSString* test = [arrayTest objectAtIndex:0];
[arrayTest removeAllObjects];
test = nil;
arrayTest = nil;
}
[testDict2 removeAllObjects];
testDict2 = nil;
}
[frameAnotationLeft removeAllObjects];
frameAnotationLeft = nil;
}
Memory when i run it is 218 mb. and it did not decrease. Someone can help me and give me solution? Thank so muuch
If you are using ARC, setting local variables to nil is unnecessary and will have no effect. Local variables are completely memory-managed for you. And setting a variable to nil does not, as you imagine, cause the memory itself to be cleared; it reduces the object's retain count, but that's all. If the object's retain count has fallen to zero, then the memory may be reclaimed at some future time, but you have no control over when that will happen. Thus, your nil setting does nothing whatever, because the same thing is being done for you by ARC anyway.
If you are concerned about accumulation of secondary autoreleased objects during a tight loop (as here), simply wrap the interior of the loop in an #autoreleasepool block.
This is my method
- (void)populateLocationsToSort {
//1. Get UserLocation based on mapview
self.userLocation = [[CLLocation alloc] initWithLatitude:self._mapView.userLocation.coordinate.latitude longitude:self._mapView.userLocation.coordinate.longitude];
//Set self.annotationsToSort so any new values get written onto a clean array
self.myLocationsToSort = nil;
// Loop thru dictionary-->Create allocations --> But dont plot
for (Holiday * holidayObject in self.farSiman) {
// 3. Unload objects values into locals
NSString * latitude = holidayObject.latitude;
NSString * longitude = holidayObject.longitude;
NSString * storeDescription = holidayObject.name;
NSString * address = holidayObject.address;
// 4. Create MyLocation object based on locals gotten from Custom Object
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
MyLocation *annotation = [[MyLocation alloc] initWithName:storeDescription address:address coordinate:coordinate distance:0];
// 5. Calculate distance between locations & uL
CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];
annotation.distance = calculatedDistance/1000;
//Add annotation to local NSMArray
[self.myLocationsToSort addObject:annotation];
**NSLog(#"self.myLocationsToSort in someEarlyMethod is %#",self.myLocationsToSort);**
}
//2. Set appDelegate userLocation
AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];
myDelegate.userLocation = self.userLocation;
//3. Set appDelegate mylocations
myDelegate.annotationsToSort = self.myLocationsToSort;
}
In the bold line, self.myLocationsToSort is already null. I thought setting a value to nil was cleaning it out basically, ready to be re-used? I need to do so because this method is called once on launch and a second time after an NSNotification is received when data is gotten from the web. If I call this method again from the NSNotification selector, the new web data gets written on top of the old data and it spits out an inconsistent mess of values :)
Setting it to nil removes the reference to that object. If you are using ARC and it is the last strong reference to that object, the system autoreleases the object and frees its memory. In either case, it does not "clean it out and be ready for re-use", you need to re-allocate and initialize your object. If you would rather just remove all of the objects, and assuming myLocationsToSort is an NSMutableArray you can just call
[self.myLocationsToSort removeAllObjects];
Otherwise you need to do
self.myLocationsToSort = nil;
self.myLocationsToSort = [[NSMutableArray alloc] init];
Im setting up a map, with some MKAnnotation. When i add them to the map, I receive this message:
An instance 0xbe62850 of class ENTAnottation was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
(
Context: 0x0, Property: 0xbe66b30>
If i am not mistaken, that means that an object has been deallocated while their observer are still alive. How can I know wich are the observers? and, even if I find them... isn't explicit deallocation forbidden with ARC? if thats true, i could not deallocate them... so... what could i do?
Thank you.
-------EDIT------
By request, I post my code. I make a call to a web that returns me a JSON with the values that I need to set my Annotations:
- (void)requestFinished:(ASIHTTPRequest *)request
{
responseString = [request responseString];
id jsonObject = [responseString objectFromJSONString];
NSLog(#"From the JSON: %#", responseString);
NSMutableArray *lat = [jsonObject valueForKeyPath:#"latitud"];
NSMutableArray *lon = [jsonObject valueForKeyPath:#"longitud"];
NSMutableArray *azafatas = [jsonObject valueForKeyPath:#"azafata"];
NSMutableArray *usernames = [jsonObject valueForKeyPath:#"username"];
NSMutableArray *mapazafatas=[[NSMutableArray alloc]init];
for(int i=0;i<[lat count]; i++)
{
ENTAnottation *azafata=[[ENTAnottation alloc]init];
double latidouble=[[lat objectAtIndex:i]doubleValue];
double longdouble=[[lon objectAtIndex:i]doubleValue];
CLLocationDegrees lati=latidouble;
CLLocationDegrees longi=longdouble;
CLLocationCoordinate2D coords=CLLocationCoordinate2DMake(lati, longi);
NSString *nombre=[azafatas objectAtIndex:i];
NSString *email=[usernames objectAtIndex:i];
azafata.coordinate=coords;
azafata.title=nombre;
azafata.username=email;
[mapazafatas addObject:azafata];
//[mapa addAnnotation:azafata];
}
for (int i=0;i<[mapazafatas count];i++)
{
[mapa addAnnotation:[mapazafatas objectAtIndex:i]];
}
}
After that, the app crashes, despite it goes through my code without any problem.
Solved. Obviously, if you pass an impossible pair of coordinates it throws to your face that very clear, and impossible to confuse anybody, error message. Pity me...
I'm almost there understanding simple reference counting / memory management in Objective-C, however I'm having a difficult time with the following code. I'm releasing mutableDict (commented in the code below) and it's causing detrimental behavior in my code. If I let the memory leak, it works as expected, but that's clearly not the answer here. ;-) Would any of you more experienced folks be kind enough to point me in the right direction as how I can re-write any of this method to better handle my memory footprint? Mainly with how I'm managing NSMutableDictionary *mutableDict, as that is the big culprit here. I'd like to understand the problem, and not just copy/paste code -- so some comments/feedback is ideal. Thanks all.
- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument
withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *mutableArray = [[[NSMutableArray alloc] init] autorelease];
//NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
CXMLDocument *theXMLDocument = [[[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError] retain];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
int i, j, cnt = [nodes count];
for(i=0; i < cnt; i++) {
CXMLElement *xmlElement = [nodes objectAtIndex:i];
if(nil != xmlElement) {
NSArray *attributes = [NSArray array];
attributes = [xmlElement attributes];
int attrCnt = [attributes count];
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
for(j = 0; j < attrCnt; j++) {
if([[[attributes objectAtIndex:j] name] isKindOfClass:[NSString class]])
[mutableDict setValue:[[attributes objectAtIndex:j] stringValue] forKey:[[attributes objectAtIndex:j] name]];
else
continue;
}
if(nil != mutableDict) {
[mutableArray addObject:mutableDict];
}
[mutableDict release]; // This is causing bad things to happen.
}
}
return (NSArray *)mutableArray;
}
Here's an equivalent rewrite of your code:
- (NSArray *)attributeDictionaries:(NSString *)xmlDocument withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *dictionaries = [NSMutableArray array];
CXMLDocument *theXMLDocument = [[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
for (CXMLElement *xmlElement in nodes) {
NSArray *attributes = [xmlElement attributes];
NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
}
[dictionaries addObject:attributeDictionary];
}
[theXMLDocument release];
return attributeDictionaries;
}
Notice I only did reference counting on theXMLDocument. That's because the arrays and dictionaries live beyond the scope of this method. The array and dictionary class methods create autoreleased instances of NSArray and NSMutableDictionary objects. If the caller doesn't explicitly retain them, they'll be automatically released on the next go-round of the application's event loop.
I also removed code that was never going to be executed. The CXMLNode name method says it returns a string, so that test will always be true.
If mutableDict is nil, you have bigger problems. It's better that it throws an exception than silently fail, so I did away with that test, too.
I also used the relatively new for enumeration syntax, which does away with your counter variables.
I renamed some variables and the method to be a little bit more Cocoa-ish. Cocoa is different from most languages in that it's generally considered incorrect to use a verb like "create" unless you specifically want to make the caller responsible for releasing whatever object you return.
You didn't do anything with theError. You should either check it and report the error, or else pass in nil if you're not going to check it. There's no sense in making the app build an error object you're not going to use.
I hope this helps get you pointed in the right direction.
Well, releasing mutableDict really shouldn't be causing any problems because the line above it (adding mutableDict to mutableArray) will retain it automatically. While I'm not sure what exactly is going wrong with your code (you didn't specify what "bad things" means), there's a few general things I would suggest:
Don't autorelease mutableArray right away. Let it be a regular alloc/init statement and autorelease it when you return it ("return [mutableArray autorelease];").
theXMLDocument is leaking, be sure to release that before returning. Also, you do not need to retain it like you are. alloc/init does the job by starting the object retain count at 1, retaining it again just ensures it leaks forever. Get rid of the retain and release it before returning and it won't leak.
Just a tip: be sure that you retain the return value of this method when using it elsewhere - the result has been autoreleased as isn't guaranteed to be around when you need it unless you explicitly retain/release it somewhere.
Otherwise, this code should work. If it still doesn't, one other thing I would try is maybe doing [mutableArray addObject:[mutableDict copy]] to ensure that mutableDict causes you no problems when it is released.
In Memory Management Programming Guide under the topic Returning Objects from Methods (scroll down a bit), there are a few simple examples on how to return objects from a method with the correct memory managment.