Objective-c for the iphone: Mystery memory leak - iphone

My application seems to have 4 memory leaks (on the device, running instruments).
The memory leaks seems to come from this code:
NSURL *url = [self getUrl:destination];
[destination release];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"GET"];
[request addValue:#"application/json" forHTTPHeaderField:#"content-type"];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
[request release];
[connection release];
EDIT: added code for getUrl
- (NSURL *)getUrl:(NSString *)actionUrl
{
NSString *rawUri = [[NSString alloc]initWithFormat:#"%#/%#", kBaseUrl, actionUrl];
NSURL *url = [[[NSURL alloc] initWithString:rawUri] autorelease];
[rawUri release];
return url;
}
I am releasing all my objects as far as I can see but it's still showing this as the source of the 4 memory leaks.
This is on the Device running 3.1.3
Is it acceptable to have a few memory leaks in your app or do they all have to go?
EDIT: I've added autorelease to getUrl. However it still shows up with memory leaks
EDIT2: The behaviour is rather strange. I launch the app and hit the button that makes this call once. 4 leaks are discovered. I press back and hit the button again, and keep doing this a few times, and still only 4 leaks. However, if I wait a few seconds and then press the button a gain a few more times, 9 leaks are discovered. It's not a small 128 byte leak, but it's 1.61KB at this point.
EDIT3: Here is the connectionDidFinishLoading
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
SBJSON *jsonParser = [[SBJSON alloc] init];
NSString *jsonString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
[receivedData setLength:0];
[receivedData release];
[self.delegate dataReceived:[jsonParser objectWithString:jsonString]]; // See method below
[jsonParser release];
[jsonString release];
}
The delegate gets the data, then transforms it (and in return passes it on to another delegate once the product is constructed)
- (void)dataReceived:(id)data
{
NSMutableArray *myObjects = [[NSMutableArray alloc]init];
ObjectFactory *objectFactory = [[ObjectFactory alloc]init];
// Only one object
if ([data isKindOfClass:[NSDictionary class]])
{
Object *object = [objectFactory buildObject:data];
[myObjects addObject:object];
[object release];
}
// Multiple objects
if ([data isKindOfClass:[NSArray class]])
{
for (NSDictionary *objectSrc in data)
{
Object *object = [objectFactory buildObject:post];
[myObjects addObject:object];
[object release];
}
}
[objectFactory release];
[self.delegate objectsReceived:myObjects];
}
EDIT4:
Something I did notice is that the object "ConnectionObject" that contains the NSUrlConnection, never seem to be deallocated.
I put a breakpoint on dealloc which calls [connection release]
This dealloc is never called. All the deallocs are called down the chain except for this one.
I tried [connection cancel] in the "connectionDidFinishLoading" call to see if that helped but not at all.
This sure is a mystery to me...

You are releasing something you shouldn't:
NSURL *url = [self getUrl:destination];
// the returned url should have been autoreleased by the getUrl: method
// so you shouldn't release it again
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[url release]; // don't do this!
Remember that you should only release objects that were created using alloc, new or retain . Objects returned from other methods are always in an auoreleased state (by convention).

Related

Both setCompletionBlock and setFailed don't get called

I'm getting acquainted with ASIHTTPRequest library and i tried to implement connection to a server. Here is my code:
- (BOOL)IsEnteredDataCorrect {
__block NSString *password;
__block NSString *responseString;
NSString *url = [NSString stringWithFormat:#"%#/login/",SERVER_URL];
__block ASIHTTPRequest *loginRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.labelText = #"Please wait";
[loginRequest setCompletionBlock:^
{
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
responseString = [loginRequest responseString];
// NSLog(#"%#",responseString);
NSData *responseData = [loginRequest responseData];
NSError *err = nil;
NSDictionary *users = [[CJSONDeserializer deserializer] deserializeAsDictionary:responseData error:&err];
password = [users objectForKey:idField.text];
}];
[loginRequest setFailedBlock:^
{
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
[delegate alertError:[loginRequest error]];
NSLog(#"failed block");
}];
[loginRequest startAsynchronous];
NSLog(#"request is over");
if ([password isEqualToString:passwordField.text])
return YES;
else
return NO;
}
The server name is fake so i expected setFailedBlock to get called. But strange thing happens: MBProgressHUD is taken away (it is possible only when one of the blocks is called) but the code in setFailedBlock is not executed. The further code is executed successfully. OK, setFailedBlock doesn't work - but then MBProgressHUD should stay on the screen and it's gone. Can anyone explain me what's going on?
If your failed block isn't running but the MBProgressHUD is vanishing then your completion block is running.
Perhaps your server name isn't as fake as you think it is? There's an awful lot of weird domain names registered...
Also, your code here:
[loginRequest startAsynchronous];
NSLog(#"request is over");
if ([password isEqualToString:passwordField.text])
return YES;
else
return NO;
You seem to have a fundamental mismatch here - your are using an asynchronous request, but appear to be expecting that the answer is there immediately.

When release methods local variables in Obj-C

I'm developing an iPhone application and I've just created this method (it's in a singleton class):
- (NSDictionary *)getLastPosts
{
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:http://example.org/last/]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSDictionary *data_dict = [parser objectWithString:json_string error:nil];
// release stuff
[parser release];
[request release];
[response release];
[json_string release];
return data_dict;
}
I'm a newbie obj-c developer so I'm not sure of this two things:
Is it correct the four vars release in the method's end?
When should I release the NSDictionary data_dict?
UPDATE 1
If data_dict was NSDictionary *data_dict = [[NSDictionary alloc] init] when I'll should release it?
UPDATE 2
In the caller I have this:
- (void)callerMethod
{
NSDictionary *tmpDict = [mySingleton getLastPosts];
NSLog(#"retain count: %d", [tmpDict retainCount]);
}
and the debug console prints:
retain count: 2
Why "Xcode Analyze" says me these lines?
And why the retain count it's 2?
In general, it is good to release objects you do not need any more.
But remember
- Only things that have alloc, new or copy in their initialization need to be released. Otherwise they are already autoreleased.
So, it is ok to release the parser, not ok to release the request, not ok to release the response, ok to release the json_string.
SBJsonParser *parser = [[SBJsonParser alloc] init];
You called init, then you own the instance and you need to release it.
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:http://example.org/last/]];
You called a class method that returns an autoreleased instance which will be added to the autorelease poll.
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
Autoreleased.
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
You called init, you will need to release it.
NSDictionary *data_dict = [parser objectWithString:json_string error:nil];
Returned instance, autoreleased.
Thus you just need to release two of them:
[parser release];
[json_string release];
if NSDictionary *data_dict = [[NSDictionary alloc] init] then you would need to autorelease it yourself: the convention is that any instance returned by a method is autoreleased.
By the way by autoreleasing it you make sure that it will be available until the autorelease pool is emptied (unless you call release on it).
To autorelease it:
return [data_dict autorelease];
It is correct to release parser and json_string because these are created with methods containing "alloc". It is incorrect to release the others because they are autoreleased.
You never have to release data_dict in this method, since it is autoreleased.
Please read the Objective-C memory management rules.

Creating a new NSURLConnection inside of connectionDidFinishLoading

I have a NSURLConnection that gets data from a JSON web service, and everything works fine. I'm using it to post something to the server and get a success response.
After that call I want to initiate another NSURLConnection to refresh the data, so I'm doing so inside the connectionDidFinishLoading method, however this second connection isn't calling connectionDidFinishLoading when it is done loading.
Can I not initiate a NSURLConnection from inside the connectionDidFinishLoading method?
EDIT: Below is the code. I subclassed NSURLConnection to include a Tag NSString, calling the new class NSURLConnectionHelper. I'm using this to differentiate which connection has called the connectionDidFinishLoading.
- (void)connectionDidFinishLoading:(NSURLConnectionHelper *)connection
{
if([connection.Tag isEqual:#"NewMessage"]){
NSString *jsonString = [[NSString alloc] initWithData:receivedNewMessageData encoding:NSASCIIStringEncoding];
NSDictionary *results = [jsonString JSONValue];
[jsonString release];
[connection release];
if ([[results objectForKey:#"MessageAdded"] isEqual:#"True"]) {
User *newUser = [[User alloc] init];
[newUser retrieveFromUserDefaults];
if([newUser IsLoggedIn]){
Message *message = (Message *)[messages objectAtIndex: 0];
NSString *urlAsString = // url for webservice goes here
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLConnectionHelper *connection1 = [[NSURLConnectionHelper alloc] initWithRequest:request delegate:self];
connection1.Tag = #"GetLatestMessages";
[request release];
if (connection1) {
receivedLatestMessagesData = [[NSMutableData data] retain];
} else {
// Inform the user that the connection failed.
}
}
}
}else if([connection.Tag isEqual:#"GetLatestMessages"]){
//do some other stuff but this code is never reached
}
}
I'm not familiar with NSURLConnectionHelper but it looks like you're never starting the connection.
I ended up having a space in my web service url, once I corrected that it worked.

NSURLConnection shown as leaking in instruments

Hello another stupid question regarding leaks and also NSURLConnection. How do i release it? Is it enough if i release in the following 2 methods?
(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
(void)connectionDidFinishLoading:(NSURLConnection *)connection
Because in instruments it shows me the line where I alloc my connection as the source of leaking.
(EDIT1: OK I don't get it. After the following code my urlConnection has a retain count of 2. WTF?)
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self];
This is the line that instruments points me to.
EDIT2: here is some code:
I create the connection here
- (void) makeRequest
{
//NSString *urlEncodedAddress = [self.company.street stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
NSString *urlString = [[NSString alloc] initWithFormat:
#"http://maps.google.com/maps/api/geocode/xml?latlng=%f,%f&sensor=false",
bestEffort.coordinate.latitude,bestEffort.coordinate.longitude];
debugLog(#"%#",urlString);
NSURL *url = [[NSURL alloc] initWithString: urlString];
[urlString release];
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL: url];
[url release];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self];
debugLog(#"connection created %# rc %i", urlConnection, urlConnection.retainCount);
[urlRequest release];
connection = urlConnection;
}
I release it here
-(void)connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error
{
debugLog(#"ERROR with the connection: %#", error.localizedDescription);
//[activityIndicator setHidden:YES];
debugLog(#"connection will be released or else %# %i", _connection, [_connection retainCount]);
[connection release];
connection = nil;
[webData release];
webData = nil;
if (!cancel)
[delegate rgc_failedWithError: self : error];
isWorking = FALSE;
}
Or here
-(void)connectionDidFinishLoading:(NSURLConnection *)_connection
{
debugLog(#"connection will be released (or else) %# %i", _connection, [_connection retainCount]);
[connection release];
connection = nil;
debugLog(#"DONE. Received Bytes: %d", [webData length]);
//NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
//debugLog(#"%#",theXML);
//[theXML release];
.....
.....
}
EDIT3: Problem solved by not caring whether it's leaking or not! Simple!
You're correct to release it in the delegate methods, however the analysis tools like instruments and the clang analyser aren't clever enough to deal with that and report a false positive. I'd be inclined to file a bug with apple, it will certainly be a duplicate but will tell them that more developers are finding this annoying.

Passing an NSArray as a parameter?

I am trying to get JSON data loaded from a NSURLConnection delegate to send the array of objects back to the tableview that called it.
The delegate object is initialized with callback to send back to
NSArray *returnArray;
ResultsTableRoot *callback;
JSON handling method
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData
encoding:NSUTF8StringEncoding];
[responseData release];
NSError *error;
SBJSON *json = [[[SBJSON alloc] init] autorelease];
returnArray = [json objectWithString:responseString
error:&error];
[responseString release];
//////////////////////////////////////////////
// Send data back to table view
[callback resultsArrayReciever:returnArray];
}
The array can't be accessed from here, the tableview I want to have the information, however the method is called
-(void)resultsArrayReciever:(NSArray *)array {
// Code executed
if(array) {
// Code never executes, array isnt there
}
}
If you have a better way to go about this whole thing, it is more than welcome!!
The returnArray is probably autoreleased. Try retain/releasing it in your methods.
If it is autoreleased the contents will be released in your run-loop and therefore disappear by the time you want to access it.
Check the NSError instance to see if there wasn't some problem while deserializing the JSON;
Try retaining the object:
NSError *error;
SBJSON *json = [[SBJSON new] autorelease];
returnArray = [[json objectWithString:responseString error:&error] retain];
[responseString release];
[callback resultsArrayReciever:returnArray];
[returnArray release];