Using NSURLConnection properly - objective-c

When using the NSURLConnection can I release it at the end of the method where it is instantiated?
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
...
...
[connection release];
Then it continue with the asynchronous process, or do I need to retain it?

You need to release connection object when you are done.
Release this in NSURLConnection delegate methods -
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

You should hold the connection by "assign", the reason is that: you need to manage the connection for example:cancel it.

I really give you the advice to use convenience allocators whenever possible. The objects you get back are autoreleased meaning (in easy words) you won't have to worry about manual releasing or retaining. To answer your question with more then a rule of thumb: use
connectionWithRequest:delegate:
and you're good to go.

The best is to use connectionWithRequest:Delegate: so that it's autoreleased.
You should however also keep a pointer to your NSURLConnection until the connection end to have the opportunity to nil the delegate. Otherwise in the case where your connection does not return but your delegate gets deallocated before being called by the connection, it will crash. ( you'll get the error: message sent to deallocated instance)
So the best is to keep a pointer to your NSURLConnection and set the pointer to nil when the connection return. So in ConnectionDidFinishLoading
connection = nil;
Now if the delegate gets deallocated you can put something in the dealloc method or in viewDidUnload if your delegate is a viewController
if (connection != nil) connection.delegate = nil;

Related

Objective C Unsure where method is being called?

Showing what a novice I am with Objective C here. The second of these two methods is getting called by the method above. Though I have absolutely no idea where? I want to be able to wrap the part that calls the second method in an if statement to determine if the file did exist based on the returned Boolean. Example code would be appreciated, if anyone could also explain how this second method gets called that would also be fantastic.
-(void) queryResponseForURL:(NSURL *)inURL {
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:inURL];
[request setHTTPMethod:#"HEAD"];
NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self];
// connection starts automatically
}
-(BOOL)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
if([(NSHTTPURLResponse *)response statusCode] == 200){
NSLog(#"file exists");
return YES;
}else return NO;
}
It is getting called by the NSURLConnection object, so you can't intervene in the code that calls it. But you shouldn't need to - the purpose of a delegate method is to enable you to hook in to that inaccessible code.
The method signature is this, by the way:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
It doesn't have a return value and you can't change the method signature, as it's part of the NSURLConnection object's protocol. Remember, you don't call this method, the connection object does. But you can respond to the method. Instead of attempting to return a BOOL, you can deal with your 'if file..' code inside this method, or call another method from it. You should bear in mind that this is triggered (asynchronously) when a response is confirmed to have been initiated - so you can check HTTP header info such as mime type - but it doesn't guarantee a completed data transfer (there are other delegate methods for that).
See also the developer guide to NSURLConnection
The second method is a delegate or callback method. The callback method is called when your connection instance receives a response from the server your connecting with. NSURLConnection has a few informal protocols on NSObject. This means that methods defined in these protocols will automatically be called when any subclass of NSObject has implemented these methods. Your subclass of NSObject has implemented one of these callback methods, therefore this method is called. Most delegates are declared as formal protocols and need to be explicitly defined in your classes in order to make use of them. The informal protocols of NSURLConnection are exception to the rule, though this is about to change in future versions of iOS.
P.S.: a protocol in Objective-C is essentially the same as an interface in Java / C#.
More info here: http://developer.apple.com/library/ios/ipad/#documentation/Foundation/Reference/NSURLConnectionDataDelegate_protocol/Reference/Reference.html
I guess you want to cancel the connection when you receive the wrong response? What you should do is the following:
Store your URLConnection in an ivar or property (personally I'd prefer a property).
In your -connection:didReceiveResponse delegate method, if the response status code isn't an appropriate value, cancel your connection. You can use your ivar or property for this purpose.

Objective-C: How can I release this object?

I have a class named "ServerDataLayer" that holds a NSURLConnection, and a NSMutableData that its writing the received HTTP data in to. When the connection finishes, it simply fires a delegate that my caller passed itself as a reference, the method looks like this:
-(void) serverDataLayerResponse:(id)entity
{
if ([entity isMemberOfClass:[LoginResponse class]])
{
LoginResponse *response = (LoginResponse*)entity;
NSLog(#"Error Code: %d", response.errorCode);
NSLog(#"Error Message: %#", response.errorMessage);
NSLog(#"Registered: %c", response.registered);
NSLog(#"AuthToken: %#", response.authToken);
[AppData shared].authToken = response.authToken;
ServerDataLayer *request = [[[ServerDataLayer alloc] initWithServer:_serverUrl delegate:self] autorelease];
[request getPlayerDetails];
//[_server getPlayerDetails];
}
}
Here's my problem...the internal _receivedData and _connection variables are currently in use whilst this delegate method is in progress. I wanted to use my same ServerDataLayer instance to fire another request off "[_server getPlayerDetails]", but the _connection and _receivedData variables internally were getting overwritten and I was getting in to a mess about when to retain/release at the right time.
So my work around was just to instantiate the ServerDataLayer each time I wanted to talk to the server. Now...in the example above, I'm instantiating the request with an 'alloc', and setting an 'autorelease' as I lose scope of this 2nd request. Will this 2nd request stay in memory whilst it's NSURLConnection is busy internally performing the request?
I'm getting a bit lost at this point on how to manage the object references for this kind of process. Any help would be appreciated.
An NSURLConnection, if used via the delegate methods will attach itself as an input to a run loop. However it won't retain its delegate. So your ServerDataLayer would be deallocated (and hopefully remember to cancel the connection). You could use object associations to give your object the same lifecycle as the URL connection, if you were suitably careful about the potential retain loop.
If you use sendAsynchronousRequest:... then you'll probably be fine anyway; assuming you reference self or any instance variable in the completion block then you'll be retained by the block and live for at least as long as it does.
There's really no need to confuse yourself over retain/release any more. Even if ARC isn't an option, you can just declare the relevant instance variables as retain properties within a class extension and use self.property notation to set new values. Retains and releases will be handled for you.
The only caveat is that you should never use dot notation in either your init or dealloc as a special case of the rule that it isn't safe to call methods on a class that's only half instantiated or is half destroyed.
Just use multiple ServerDataLayer instances.

Random BAD_ACCESS and "not recognized selector"

Hey, I'm a beginner in Objective C, and my .NET and Java years have rusted my memory management skills, so it's very likely I'm missing something here.
I am building an iPad app. The main view is a SplitView with a TableView on the left, and the detail view contains another TableView. The loading of the latter with data has been commented out in an attempt to single out my problem.
The app seems to work fine (has to fetch data from a .NET WS and parse it into the table), but at random times I receive a BAD_ACCESS or a "selector not recognized" errors.
The selector not recognized error I get here:
-(void) connection:(NSURLConnection *) connection
didReceiveResponse:(NSURLResponse *) response {
[webData setLength: 0];
}
This piece of code I'm sure most of you know about, I got it from all samples I found online and in books to send a web request.
Beats me why it says it does not recognize the setLength selector, webData is defined as
NSMutableData *webData;
Any ideas?
Thanks.
If you do not allocate your webData object either with
NSMutableData* webData = [[NSMutableData alloc] initWithCapacity:2048];
or
NSMutableData* webData = [[NSMutableData data] retain];
then the webData object will most likely be autoreleased during one of the context switches from the NSURLConnection message that you allocated it in (probably connection:didReceiveData:) to the connection:didReceiveResponse: message.
Any object that you do not alloc or explicitly retain is likely to be deallocated during scope changes, even if it is a member variable of your class.
Most likely you aren't creating the NSMutableData correctly. I expect you have code that looks like
webData = [NSMutableData data];
This is going to give you an autoreleased object, and yet you're storing it in an ivar. You need to take ownership of the object when storing it in an ivar. In your case, the simplest way is just to skip the convenience method and go with alloc/init:
webData = [[NSMutableData alloc] init];
For more details, read the Memory Management Programming Guide.
Seems to be very usual (not only beginners) error, when connection is not cancels in dealloc or viewWillDisappear. When you are leaving the controller, you should cancel all connection, timers, etc, created by the controller, to prevent them from calling delegate methods or selectors on deallocated controller objects.
It looks like webData is being deallocated and replaced with some other object. Make sure you retain it if you don't use alloc/init or mutableCopy to get it.

Does a NSURLConnection retain its delegate?

Summary of my question: Does NSURLConnection retain its delegate?
Detailed question and scenario:
I have a custom class, called JsonDownloader that is takes in a URL and returns an NSDictionary of the JSON that the URL returns.
On an iPhone app, I do something like this. (the init method kicks off the whole process)
- (void)viewDidLoad {
JsonDownloder *temp = [[[JsonDownloader alloc] initWithURL:urlString returnDataTo:self]];
[temp release];
[super viewDidLoad];
}
When the JsonDownloader is done downloading and parsing, it performs a callback to the returnDataTo: object, in this case, the calling object.
This works just fine. Even if I introduce a 30 second delay in my web servers response, the JsonDownloader still exists and does it's callback correctly.
So my questions is this: What is keeping JsonDownloader way past the end of the event cycle? I am explicitly releasing it.
My hunch is that NSURLConnection must do a retain on its delegate, but I didn't see anything in the documentation. Anyone have an ideas?
There aren't many setters that don't either copy or retain a variable being passed to it, lest the memory of said variable be re-allocated to something else when its retain count reaches zero.
However, the answer is YES, it does. A little bit of test code shows the delegate's retain count go up:
NSLog(#"Retain count before: %d", [self retainCount]);
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com"]];
NSURLConnection* conn = [NSURLConnection connectionWithRequest:request delegate:self];
NSLog(#"Retain count after: %d", [self retainCount]);
which produces in the log:
Running…
2009-07-09 02:13:40.516 delegateRetain[45123:a0f] Retain count before: 1
2009-07-09 02:13:40.525 delegateRetain[45123:a0f] Retain count after: 2
Debugger stopped.
So you can see pretty clearly that in connectionWithRequest:delegate: "self" is indeed having its retain count increased +1. If you're feeling brave and want to mess with the EXC_BAD_ACCESS gods, add in
[conn dealloc];
NSLog(#"Retain count after dealloc: %d", [self retainCount]);
which will print out "1" again, showing a post dealloc decrement. However, you'll get a nice Program received signal: “EXC_BAD_ACCESS”. because the NSAutoreleasePool will try to release the connection and it will be gone ;)
Most delegate properties are not retained but assigned to prevent circular references. See this question about that as well.
However, NSUrlConnection does not have a specific delegate property. You have to specify the delegate along with the initialization of the connection. I think that is why it does receive a retain, as Dave Martorana showed.
Yes, "The connection retains delegate. It releases delegate when the connection finishes loading, fails, or is canceled," according to the Xcode Documentation for -[NSURLConnection initWithRequest:delegate:] under Special Considerations. See also: NSURLConnection inherent memory leak?

NSNetService delegates not being called

I'm trying to resolve a NSNetService (named "My_Mac") to an IP in a background app with this code:
NSNetService *service = [[NSNetService alloc] initWithDomain:#"local." type:#"_daap._tcp" name:#"My_Mac"];
[service setDelegate:self];
[service resolveWithTimeout:5];
And in the same class, I have these delegate methods defined:
- (void)netServiceDidResolveAddress:(NSNetService *)sender
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
Here's the strange part: neither delegate methods get called unless I run a NSAlert after "[service resolveWithTimeout:5];". Any ideas?
If you are using ARC you need to retain the service object somewhere.
In your example, the service object most likely goes out of scope and is not referenced anywhere else in your code, so the compiler will try to release it immediately after the resolve call.
Add a property:
#property (nonatomic, strong) NSMutableArray *services;
In your delegate, or wherever you use NSNetService
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didFindService:(NSNetService *)aNetService
moreComing:(BOOL)moreComing
{
if (!self.services) {
self.services = [[NSMutableArray alloc] init];
}
[self.services addObject:aNetService];
[aNetService setDelegate:self];
[aNetService resolveWithTimeout:3.0];
}
Don't forget to stop and release those services once you're finished or getting rid of the delegate:
for (NSNetService* service in self.services) {
[service stop];
}
[self.services removeAllObjects];
I'm not sure, but it looks like the request is not actually scheduled in a run loop for some reason. Maybe try something like this to schedule it?
NSNetService *service = [[[NSNetService alloc] initWithDomain:#"local." type:#"_daap._tcp." name:#"My_Mac"] autorelease];
[service setDelegate:self];
[service scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:#"PrivateMyMacServiceMode"];
[service resolveWithTimeout:8.0];
Stupid question, but are you explicitly implementing NSServiceDelegate protocol or just have the methods?
EDIT: I had another thought that this might be some kind of race condition (a more likely scenario). Delegates are usually weak references. If your object is dropping out of scope and being autoreleased, the system would end up with a nil handle and be firing the messages to nil. In the case where you show an NSAlert (or do other work) your object might be hanging around just long enough for it to get the messages fired back to it. Could you confirm your object sticks around for the full 8 seconds?
I've just run into this problem and I was already retaining the NSNetService object so that wasn't the reason.
Turns it out it was because the access levels of the delegate methods I defined wasn't matching the access level of the class (the class was public while the delegate methods were internal). And because all of the delegate methods are optional, the compiler was just giving a silent warning.