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.
Related
I'm developing an iPhone application and have some trouble with my xml parser. I have to check multiple values from multiple XML files, but when the XML parser is active I can't do anything else. This is not how I want it, because checking the xml must be done in the background, without being noticed. Here is some of my code, hope it's enough!
appDelegate.datavalues = [[NSMutableArray alloc] init];
for(int i = 0; i < [headarray count]; i++){
NSMutableArray *infoarray = [[NSMutableArray alloc]initWithArray:[headarray objectAtIndex:i]];
NSString *IP = [infoarray objectAtIndex:1];
NSString *Unique = [infoarray objectAtIndex:2];
NSString *Port = [infoarray objectAtIndex:3];
NSString *relay = (NSString *)[infoarray objectAtIndex:4];
NSString *input = (NSString *)[infoarray objectAtIndex:5];
NSLog(#"relay%#",relay);
NSString *urlAddress = [NSString stringWithFormat:#"http://%#:%#/state.xml",IP,Port];
NSURL *url = [NSURL URLWithString:urlAddress];
NSString *authHeader = [NSString stringWithFormat:#"Basic %#",Unique];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval: 3];
[request setValue:authHeader forHTTPHeaderField:#"Authorization"];
//NSURLConnection *connectionResponse = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSURLResponse *myURLResponse;
NSError *myError;
NSData* myDataResult = [NSURLConnection sendSynchronousRequest: request returningResponse:&myURLResponse error:&myError];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:myDataResult];
XMLParser *parser = [[XMLParser alloc] initXMLParser];
//parser.relay = [infoarray objectAtIndex:4];
//Set delegate
[xmlParser setDelegate:parser];
//Start parsing the XML file.
BOOL success = [xmlParser parse];
after this I check some values so I don't think that's necessary to show!
You could refactor the XML-related code into a separate method and then you can use Grand Central Dispatch to run the method in background:
- (void) startOperation
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0), ^{
[self runSomeXMLChecks];
dispatch_sync(dispatch_get_main_queue(), ^{
// This is dispatched on the main queue so that
// you can update the UI. The NSLog is just an example.
NSLog(#"XML check done!");
});
});
}
Have a look at the NSOperation and NSOperationQueue APIs and/or the Concurrency Programming Guide. (Both are in the Xcode library).
From the docs:
The NSOperationQueue class regulates the execution of a set of
NSOperation objects. After being added to a queue, an operation
remains in that queue until it is explicitly canceled or finishes
executing its task. Operations within the queue (but not yet
executing) are themselves organized according to priority levels and
inter-operation object dependencies and are executed accordingly. An
application may create multiple operation queues and submit operations
to any of them.
Never touched json before. I'm trying to access some variables within the Wunderground weather API for Melbourne. For example, let's say I want to access the "wind_dir":"East" variable. This is my code thus far:
NSString *urlString =
[NSString stringWithFormat:
#"http://api.wunderground.com/api/key/geolookup/conditions/forecast/q/-33.957550,151.230850.json"];
NSLog(#"URL = %#", urlString);
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSArray *weatherInfo = [parser objectWithString:json_string error:nil];
for (NSDictionary *weatherString in weatherInfo)
{
NSLog(#"some weather info = %#", [[[weatherString objectForKey:#"response"] objectForKey:#"current_observation"] objectForKey:#"wind_dir"]);
}
My code reaches the for loop and crashes with this error: -[NSCFString objectForKey:]: unrecognized selector sent to instance.
I'm not 100% sure what's causing the crash, and whether my path to the "wind_dir" variable is correct, though they could well be the same problem.
Thanks in advance for any help.
either the "response" property or the "current_observation" propery is string and not dictionary.
the error you are getting is that you are trying to call "objectForKey" on a string.
after looking at the result of the API, it seems that you are not getting an array.
You should do something like this:
NSDictionary *weatherInfo = [parser objectWithString:json_string error:nil];
NSLog(#"some weather info = %#", [[weatherInfo objectForKey:#"current_observation"] objectForKey:#"wind_dir"]);
instead of your for statement.
NSError *theError = nil;
NSArray *keys = [NSArray arrayWithObjects:#"password", #"userId", nil];
NSArray *objects = [NSArray arrayWithObjects:passwordTextField.text, userNameTextField.text, nil];
NSDictionary *requestDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
NSString *JSONString =[requestDictionary JSONRepresentation];
NSData *JSONData =[JSONString dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"JSONString :%#", JSONString);
NSLog(#"JSONData :%#", JSONData);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://153.20.32.74/11AprP306/passenger/jsonitem"]];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:JSONData];
[request setHTTPMethod:#"POST"];
NSURLResponse *theResponse =[[NSURLResponse alloc]init];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&theResponse error:&theError];
NSLog(#"response : %#", theResponse);
NSLog(#"error : %#", theError);
NSLog(#"data : %#", data);
NSMutableString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"string: %#", string);
[string release];
//[theResponse release]; // this statement crashes the app
Has it got something with to do with this statement :NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&theResponse error:&theError];
I see a & symbol used. What does it means?
I'll post this as a new answer as you edited your question.
You are doing it the wrong way.
It is the responsability of sendSynchronousRequest:returningResponse:error: to create the response for you (or the error if something went wrong)
This is why you need to pass a pointer to theResponse and a pointer to theError
When the call to sendSynchronousRequest:returningResponse:error: is done, theResponse will be created and most importantly autoreleased (by sendSynchronousRequest:returningResponse:error) !!
So in the end you are back to the autorelease/over release issue.
The correct code is:
NSURLResponse *theResponse = nil; // no need to init it will be done later on
NSError *theError = nil; // no need to init either
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&theResponse
error:&theError];
if (aError != nil) { } // handle error
else {} // handle your response data
//no need to release theResponse
Well, you autorelease theResponse when you instantiate it, so releasing it twice is causing your problem. Either don't make the autorelease call or don't make the release call.
Personally, I'd get rid of the autorelease. release gives finer-grained control over the run of your program.
Oh, and the & there is nothing to worry about -- it just passes the address of the variable it proceeds. In this case, you need to pas an NSURLResponse**. Since you have an NSURLResponse*, you pass a reference to it.
This is because theResponse has sent the message autorelease in:
NSURLResponse *theResponse =[[[NSURLResponse alloc]init] autorelease];
If you release an object that has been autoreleased you will cause your application to crash for the Garbage Collector will over release the object.
The & simply means "give me the address of theError and theResponse (basically you are passing a pointer of pointer which is required by the method sendSynchronousRequest:returningResponse:error:)
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request
returningResponse:(NSURLResponse **)response
error:(NSError **)error
The NSURLResponse ** and NSError ** means 'address of address' so give them only theError or theResponse (without the &) would simply give the method 'their address' when it is expecting something else.
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).
Should I be retaining the responseData that I am returning
// METHOD
-(NSData *)dataFromTurbine:(NSString *)pathToURL {
NSURL *url = [[NSURL alloc] initWithString:pathToURL];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
[request release];
[url release];
return responseData;
}
.
// CALLED
NSData *newData = dataFromTurbine(kTurbineDataPath);
[doSomething newData];
Since the method name doesn't start with init, new or copy, dataFromTurbine should return an autoreleased instance of NSData. (Which is already true now for responseData)
The calling method then has ownership, and should retain if needed.
In a word, no.
The NSData object you get from NSURLConnection is autoreleased, so you should retain/release it only if you need to keep it. Otherwise, it will be automatically released for you at the next pass of the run loop.