As a disclaimer I'd like to state that I'm fairly new to Objective-C and Cocoa. Currently I'm trying to write a basic application that can POST XML data to a particular endpoint. To achieve this, I've created a ServiceRouter class which uses NSURLConnection to post XML data to a particular URL.
The ServiceRouter class is intended as a base for subclasses which contain webservice-specific XML queries. In the example below, I subclass ServiceRouter to create a ServiceImplementation class.
When it's time to generate and POST the XML, I create an instance of the ServiceImplementation class like so:
[[ServiceImplementation alloc] createServiceSpecificXML];
This all seems to work fine. The issue is that Leaks reports a number of issues. Being fairly inexperienced, I'm not really sure where to start. For the most part, the NSURLConnection code is lifted from Apple's documentation.
Following basic memory management rules, I imagine I will have to release my ServiceImplementation instance at some point. What I'm confused about is how this should be handled given the asynchronous nature of NSURLConnection. Is this a candidate for autorelease?
I'm hoping that someone with more Objective-C/Cocoa experience can look things over and tell me if I'm moving in the right direction.
Here's the ServiceRouter class:
#interface ServiceRouter : NSObject {
NSMutableData *receivedData;
}
-(void)postXMLToURL:(NSString *)url xml:(NSString *)xmlData;
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
-(void)connectionDidFinishingLoading:(NSURLConnection *)connection;
#end
#implementation ServiceRouter
- (void)postXMLToURL:(NSString *)url xml:(NSString *)xmlData
{
NSLog(#"Posting XML to URL: %#", url);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/xml" forHTTPHeaderField:#"Content-type"];
[request setValue:#"application/xml" forHTTPHeaderField:#"Accept"];
[request setValue:[NSString stringWithFormat:#"%d", [xmlData length]] forHTTPHeaderField:#"Content-length"];
[request setHTTPBody:[xmlData dataUsingEncoding: NSUTF8StringEncoding]];
NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
if(connection) {
NSLog(#"Connection created");
receivedData = [[NSMutableData data] retain];
} else {
NSLog(#"Issue with connection!");
}
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if([response respondsToSelector:#selector(statusCode)])
{
int statusCode = [((NSHTTPURLResponse *)response) statusCode];
NSLog(#"HTTP Response code: %i", statusCode);
}
NSLog(#"Received response");
[receivedData setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Received data: %#", data);
[receivedData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[connection release];
[receivedData release];
NSLog(#"Connection failed! Error - %#", [error localizedDescription]);
}
-(void)connectionDidFinishingLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data", [receivedData length]);
[connection release];
[receivedData release];
}
#end
Here's my ServiceImplementation class:
#interface ServiceImplementation : ServiceRouter {
}
-(void) createServiceSpecificXML;
#end
#implementation ServiceImplementation
-(void) createServiceSpecificXML
{
NSString *xmlData = #"<example><ignore/></example>";
[super postXMLToURL:#"http://site.com/endpoint.xml" xml:xmlData];
}
#end
You never initialize the instance. Merely allocing is not sufficient. You must call init or some other initializer — preferably on the same line as alloc.
From your (working but odd) construct of [[NSMutableData data] retain], I'm going to guess you haven't read a lot of Apple's basic primers. I would recommend at least The Objective-C Programming Language and the memory management guide. Neither is very long, and between these two, I think you'll clear up a lot of your uncertainties.
Related
I Have a NSURLConnection that has been working for a while and all of a sudden is not working.
For some reason, the only delegate method that gets called is:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:
None of the other delegate methods get called. I have other classes which uses pretty much the same code, just a different request and url and they all seem to work fine. I have read alot of post that talk about making sure the connection is on the same thread as the delegate etc but nothing seems to work for me. I know the server is returning a response because if I pass the same information through a simple html form I get a response in my browser, and I can see evidence that my server side script is running because I can see the changes it is making in the SQL data. And the app is getting some sort of resonse, its just not getting any data or calling the connectionDidFinishLoading delegate method.
Any ideas of what the problem might be?
Here is a simplified version on my code:
#import "RegistrationViewController.h"
#interface RegistrationViewController ()
#end
NSMutableData *responseData;
NSURLConnection *theconnection;
#implementation RegistrationViewController
/*
* Attempt to Register online. Returns False if valiation failed
*/
- (BOOL) registerOnline{
// OTHER CODE HERE TO BUILD DATA FOR THE REQUEST
// URL Request
NSURL *requestUrl = [NSURL URLWithString:THEURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:requestUrl];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[NSData dataWithBytes:[bodyData UTF8String] length:strlen([bodyData UTF8String])]];
// Initialse Response Object
responseData = [[NSMutableData alloc] init];
// Conection
theconnection = [NSURLConnection connectionWithRequest:request delegate:self];
[theconnection start];
return YES;
}
/*
* Handle the event of registration failing
*/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"didFailWithError");
// OTHER CODE HERE TO HANDLE THE ERROR....
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"did receive response ");
}
/*
* Handle the reciept of Data
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(#"didReceiveData");
// Add data to the response
[responseData appendData:data];\
}
/*
* Data finnished loading
*/
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connectionDidFinishLoading");
// OTHER CODE HERE TO HANDLE THE RESPONSE....
}
You should look at the response and diagnose what's going on. For example, if statusCode is not 200, you might have a problem. See the HTTP/1.1 Status Code Definitions.
So, you might check the statusCode:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode == 200) {
NSLog(#"received successful (200) response ");
} else {
NSLog(#"whoops, something wrong, received status code of %d", statusCode);
}
} else {
NSLog(#"Not a HTTP response");
}
}
You're also calling:
theconnection = [NSURLConnection connectionWithRequest:request delegate:self];
That starts the connection automatically. By calling
[theconnection start];
you're starting it a second time. Remove the start method, or use initWithRequest:delegate:startImmediately: with NO for that final parameter.
If it can help anybody, the problem for me was that i was starting the connection in an another thread
So be sure to call
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
or
theconnection = [NSURLConnection connectionWithRequest:request delegate:self];
[theconnection start];
in the main thread.
my app is using network connections as in here:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
.
.
.
receivedData = [[NSMutableData alloc] init];
}
.
-(void) dataFromWeb{
request = [[NSURLRequest alloc] initWithURL: url];
theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (theConnection) {
receivedData = [[NSMutableData data] retain];
NSLog(#"** NSURL request sent !");
} else {
NSLog(#"Connection failed");
}
.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
.
.
.
[theConnection release];
[request release];
// Here - using receivedData
[receivedData release];
}
.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[receivedData setLength:0];
}
.
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
[theConnection release];
[request release];
[receivedData release];
}
.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[receivedData appendData:data];
}
. Down the road in the app is this snippet (onButtonPressed):
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [[NSMutableData data] retain];
} else {
NSLog(#"Connection failed");
}
What I want to understand is:
If I wanted to create another simultaneous URLRequest, would I need to use a different connection so that data arriving from the web won't mix up upon retrieval?
In this code, what happens is that sometimes the code would crash on the line of the function didReceiveResponse() setLength:0, when the app crashed I saw receivedData=nil should I change the line to
if(receivedData!=nil)
[receivedData setLength:0];
else
receivedData = [[NSMutableData data] retain];
I am not so sure what this line is doing receivedData = [[NSMutableData data] retain];
I think that the easiest way to handle 2 connections is duplicating the NSMutableData. I use in that way, and it works perfectly for me.
Fist you do this in the point you want the second connection:
receivedData2 = [[NSMutableData alloc] init];
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (receivedData2) {
[receivedData2 setLength:0];
}
else
{
[receivedData setLength:0];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (receivedData2) {
[receivedData2 appendData:data];
}
else {
[receivedData appendData:data];
}
}
And then, in the method you ask for receivedData2 or receivedData
And when you use it don´t forget to do:
receivedData2=nil;
[receivedData2 setLength:0];
You would of course need a different NSURLConnection.
You can do that, but better find out, why it is nil, because it shouldn't.
It allocates an empty NSMutableData object. But this seems like ugly code for me, because you create an autoreleased object and retain it. Better write [NSMutableData new] or [[NSMutableData alloc] init].
I write iPhone application. In this app, I use Twitter framework. In this framework, call back function made in desynchronization is in other thread.
In my view controller,
ViewController.m
[accountStore requestAccessToAccountsWithType:accountType
withCompletionHandler:^(BOOL granted, NSError *error) {
if (granted) {
if (account == nil) {
NSArray *accountArray = [accountStore accountsWithAccountType:accountType];
account = [accountArray objectAtIndex:2];
}
if (account != nil){
NSURL *url = [NSURL URLWithString:#"http://api.twitter.com/1/statuses/user_timeline.json"];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"1" forKey:#"count"];
TWRequest *request = [[TWRequest alloc] initWithURL:url
parameters:params
requestMethod:TWRequestMethodGET];
[request setAccount:account];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (responseData) {
//Throw response data to other Web API
[self otherAPI:responseData];
[[NSRunLoop currentRunLoop] run];
}
}];
}
}
}];
And I write these method in this class.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
But I cannot receive full data from other API. I can receive only first data. I think there are some problems in conducting multi thread.
Therefore I'd like to let me know what's wrong in this code.
I think I see your problem. -connection:didReceiveData: is called multiple times, you need build up a NSMutableData object which will contain the whole message.
Note: This only works for a single download per instance at one time.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.responseData = [[NSMutableData dataWithCapacity:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// self.responseData has all the data.
}
A short explanation what I want to do: I'm using NSURLConnection to connect to a SSL webpage which is my API. The servers certificate is a self signed one so you have to accept it, for example in a web browser. I've found a solution on Stack Overflow how to do the trick (How to use NSURLConnection to connect with SSL for an untrusted cert?)
So I've added the NSURLConnection delegate to use methods like "didReceiveAuthenticationChallenge". As a result of that I cannot use this:
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
because there is no possibility to use the delegate functions in this case. My question is the following: I need a function which looks like this:
- (NSDictionary *)getData : (NSArray *)parameter {
[...|
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[...]
return myDictionary;
}
how can I return a NSDictionary by using this? As far as you know the delegate function of NSURLConnection are called now and the response isn't available at this point. The problem is that the view controller depends on this response so I need to return the dictionary directly... Does anybody know a solution for this? What about a callback function?
okay, I've found a solution for that. A very good thing is to use blocks in objective-c.
First of all you have to add some methods to NSURLRequest and NSURL:
#implementation NSURLRequest (URLFetcher)
- (void)fetchDataWithResponseBlock:(void (^)(FetchResponse *response))block {
FetchResponse *response = [[FetchResponse alloc] initWithBlock:block];
[[NSURLConnection connectionWithRequest:self delegate:response] start];
[response release];
}
#end
#implementation NSURL (URLFetcher)
- (void)fetchDataWithResponseBlock:(void (^)(FetchResponse *response))block {
[[NSURLRequest requestWithURL:self] fetchDataWithResponseBlock:block];
}
#end
And than just implement the follwing class:
#implementation FetchResponse
- (id)initWithBlock:(void(^)(FetchResponse *response))block {
if ((self = [super init])) {
_block = [block copy];
}
return self;
}
- (NSData *)data {
return _data;
}
- (NSURLResponse *)response {
return _response;
}
- (NSError *)error {
return _error;
}
- (NSInteger)statusCode {
if ([_response isKindOfClass:[NSHTTPURLResponse class]]) return [(NSHTTPURLResponse *)_response statusCode];
return 0;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
_response = response;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (!_data) _data = [[NSMutableData alloc] init];
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_block(self);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
_error = error;
_block(self);
}
Now you can do the follwing, some kind of callback function:
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://..."];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:#"application/json" forHTTPHeaderField:#"accept"];
[request fetchDataWithResponseBlock:^(FetchResponse *response) {
if (response.error || response.statusCode != 200)
NSLog(#"Error: %#", response.error);
else {
//use response.data
}
}];
Here you can find the orginal german solution by ICNH: Asynchrones I/O mit Bloecken
Thank you very much for this!
My suggestion would be to use some other delegate methods for NSURLConnection like connection:didReceiveResponse: or connection:didReceiveData:. You should probably keep a use a set up like so:
#interface MyClass : NSObject {
…
NSMutableData *responseData;
}
…
#end
- (void)startConnection {
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (theConnection) {
responseData = [[NSMutableData data] retain];
} else {
// connection failed
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// connection could have been redirected, reset the data
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// connection is done, do what you want
………
// don't leak the connection or the response when you are done with them
[connection release];
[responseData release];
}
// for your authentication challenge
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace (NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
if ([trustedHosts containsObject:challenge.protectionSpace.host])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
I am facing an annoying problem. I have an application who is basicly made of several methods:
viewDidload, connection:didReceiveResponse, connection:didReceiveData...
In my viewDidload, I define a NSURLRequest to a personal websiten, and right after and before it I added a label.text=#"xxx". I know the problem doesn't come from linking the label in IB because it used to display what I wanted.
But now it seems none of those two label.text instructions are working even though I know my NSURLRequest works because the number of bytes received changes when I change the website... Why is that ? And I'm guessing the other instructions that come after aren't working either.
I will give more details when I can in case anyone can enlighten me on this.
Have a good day and thanks for your help
- (void)viewDidLoad {
[super viewDidLoad];
label.text=#"rrr";
request=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://mywebsite.aspx?example=5"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
label.text=#"aeza";
NSURLConnection *connection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) {
receiveddata=[[NSMutableData data] retain];
label.text=#"NO BUG";
}
else {
label.text=#"BUG";
}
datastring = [[NSString alloc] initWithData:receiveddata encoding:NSUTF8StringEncoding];
components=[datastring componentsSeparatedByString:#"|"];
label.text=datastring;
[datastring release];
}
-(void) connection:(NSURLConnection *)connection didReceiveResponse: (NSURLResponse *)response
{
[receiveddata setLength:0];
}
-(void) connection: (NSURLConnection *)connection didReceiveData: (NSData *)data
{
[receiveddata appendData:data];
}
-(void)connection: (NSURLConnection *)connection didFailWithError:(NSError *)error
{
[connection release];
[receiveddata release];
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[receiveddata length]);
[connection release];
[receiveddata release];
}
#end
I would move that setup logic to -viewWillAppear, rather than the -viewDidLoad.
Nevermind, I got this to work by moving instructions to another method.