Obj-C, how to stop NSURLConnection behind NSData? - objective-c

how can I stop NSURLConnection behind NSURLConnection ?
code:
NSData *sendreq = [NSURLConnection sendSynchronousRequest:urlreq returningResponse:nil error:nil];
NSString *responseStr =[[NSString alloc] initWithBytes:[sendreq bytes] length:[sendreq length] encoding:NSUTF8StringEncoding];
I use the NSData to store the response from my server, but in this way I cant use "[sendreq cancel]"
so if the NSURLConnection is making a long connection and the user is go to anther VC I want to stop the NSURLConnection, so how can I do it ?

If you want to be able to cancel your request, do not use sendSynchronousRequest, but instead use the asynchronous, delegate-based rendition NSURLConnection. (Frankly, you should avoid using synchronous requests, from the main thread at least, for a variety of reasons.) If you use the delegate-based rendition, you can then call NSURLConnection method cancel when needed.
So, define properties to hold the data and connection reference:
#property (nonatomic, strong) NSMutableData *responseData;
#property (nonatomic, weak) NSURLConnection *connection;
Then start the connection:
self.responseData = [NSMutableData data];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlreq delegate:self];
self.connection = connection;
You obviously have to implement the NSURLConnectionDataDelegate methods:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *responseStr = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
// you can use `responseStr` here
// now that we're done with `responseData`, we might want to release it
self.responseData = nil;
}
You want to detect/handle errors, too, with the NSURLConnectionDelegate method:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// do whatever you want when error occurs
NSLog(#"%s: %#", __FUNCTION__, error);
}
If you need to cancel it, it's
[self.connection cancel];
You could also use AFNetworking, which uses delegate-based NSURLConnection, but keeps you out of the weeds of the implementation.
You can also use the newer NSURLSession which offers block-based renditions that are still can be cancelled. But that depends upon what OS versions you're trying to support.

Related

Cannot call method in objective c

I'm having difficulty understanding how to call a method. Here's the code:
- (void)beachJSON
{
// Build the string to call Beach API
NSString *urlString = [NSString stringWithFormat:#"http://nrw-bwq-dev-api.azurewebsites.net/api/Pull"];
// Create NSURL string from formatted string
NSURL *url = [NSURL URLWithString:urlString];
// Setup and start async download
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection release];
[request release];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Store incoming data into a string
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
results = [jsonString JSONValue];
}
- (NSArray *)getBeachList {
[self beachJSON];
return results;
}
I call getBeachList from another class to populate 'results', beachJSON is called fine but I need to call
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
Within
- (NSArray *)getBeachList
i.e. right here
- (NSArray *)getBeachList {
[self beachJSON];
//This is where I want to call it to populate results before it is returned
return results;
}
Calling getBeachList will call beachJSON but the connection method will be skipped over leaving 'results' nil
If i try simply putting
[self connection:(NSURLConnection *)connection didReceiveData:(NSData *)data]
I get a expected expression error on that line.
I'm pretty new to objective c so any help would be great.
You don't need to call this method.
It's a delegate method of NSURLConnection.
What you need is to set a delegate for connection and method will be called by connection when it downloads any data.
You should not call connection:didReceiveData: by yourself. This is an event which is only supposed to be fired by the NSURLConnection instance. As far as I can see the only thing you are missing is the start call, which actually makes the NSURLConnection perform the request.
Try this:
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
Then you should see that your NSURLConnection instance will call your implementation of connection:didReceiveData:.
If you want to be sure that your data is fully loaded you should be using this event instead:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
The connection:didReceiveData: will be called multiple times if your data size is large enough. Every time the NSURLConnection receives data it will invoke connection:didReceiveData:. connectionDidFinishLoading: will only be called once when all your data is ready.

HTTPS data returned not saving

I have an object class set up called WebCalls. In this class, I make a web call and return some JSON from an HTTPS server. Now the methods work perfectly, I have tested and the data returns fine. However my problem is, I can't access the data returned outside the class.
The code to retrieve the data is below
Interface
#interface WebCall : NSObject{
NSString *phoneNumber;
NSString *jsonData;
}
#property (nonatomic, retain) NSMutableData *responseData;
#property (nonatomic, retain) NSString *jsonData;
-(void) getData: (NSString *) link;
#end
Implementation
#implementation WebCall
#synthesize jsonData;
#synthesize responseData;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
self.responseData = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *s = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
jsonData = s;
}
-(void) getData: (NSString *) link{
jsonData = [[NSString alloc] init];
self.responseData = [NSMutableData data];
NSURL * url = [NSURL URLWithString:link];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPMethod:#"GET"];
[[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
}
#end
In interface class I have a string called jsonData. I get and set it using property and synthesise. So after I make web call, I assign the data to jsonData, and I should be able to import web call class, use the getInfo method, have the jsonData returned and then access it using
WebCall *wc = [[WebCall alloc] init];
[wc getData:url];
NSLog(#"%#", [c jsonData]);
However this just prints out null. And yet if I print out the String in the Webcall class after I recieve the data, it prints out fine. Could anyone tell me what I am doing wrong?
Thanks in advance
Edit: Updated with complete implementation
Also I can't access the string outside the method. I copied the code to another class, and tried assigning the JSON String, then calling it again in the body, and it comes out null again. Seems I can only print it out in that connection method. Then it seems to clear the String
Edit: What I tried
[wc setWebCallDidFinish:^(NSString * json, NSString *test){
NSLog(#"%#", json);
}];
[wc getData:#"12345"];
Adam the reason jsonData is an empty string is because [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES]; runs asynchronously which means on a new thread and it doesn't block. this means that when you call [wc getData:url]; and then immediately call NSLog(#"%#", [wc jsonData]); the http request hasn't completed yet and the - (void)connectionDidFinishLoading:(NSURLConnection *)connection delegate function hasn't been called yet in your WebCall.
For a detailed explanation read this iOs Concurrency Programming Guide. Essentially you need to add a notifier to your WebCall so that it can notify the object which spawns it that the request has finished loading. I would use a block like so.
#interface WebCall : NSObject{
NSString *phoneNumber;
NSString *jsonData;
void(^webCallDidFinish)(NSString *jsonData, id otherRandomVar);
}
#property (nonatomic, retain) NSMutableData *responseData;
#property (nonatomic, retain) NSString *jsonData;
-(void) getData: (NSString *) link;
-(void)setWebCallDidFinish:(void (^)(NSString *, id))wcdf;
#end
Implementation
#implementation WebCall
#synthesize jsonData;
#synthesize responseData;
-(void)setWebCallDidFinish:(void (^)(NSString *,id))wcdf{
webCallDidFinish = [wcdf copy];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *s = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
jsonData = s;
webCallDidFinish(jsonData, #"any other object");
}
//all of your other code here
Then in the calling code the following
WebCall *wc = [[WebCall alloc] init];
[wc setWebCallDidFinish:^(NSString * json, id randomSecondVar) {
NSLog(#"%#",json);
}];
[wc getData:url];
What will happen is the block of code you provide to setWebCallDidFinish will be called after jsonData is loaded. You could also use the Delegate pattern to accomplish this. Note that while this asynchronous request is loading you should provide some sort of indicator to your user.

how to fetch page content of an URL Request (post)

I have a little Mac application which should be able to post Data to my web server which saves the data in a database. Now that's the Code I have now:
NSData *postData = [urlString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"http://..."]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) NSLog(#"Done");
And it works fine. But now I want to check whether the data was correct (and stored in the database) or something (like eMail) is wrong. The PHP file prints e.g. "email incorrect" out if the E-Mail is not correct.
But how can I fetch this data (which PHP prints out) in Xcode that the App knows whether it was successful or not?
Thanks for answers!
You need to implement the NSURLConnectionDelegate methods connection:didReceiveData: and connectionDidFinishLoading:
According to the docs didReceiveData: may be called multiple times per NSURLRequest (i.e. the response will not always arrive all at once) so the recommended method is to append the incoming data to buffer during connection:didReceiveData: and then do any processing on the data in connectionDidFinishLoading:.
You could create a property on your class like this:
#property (nonatomic, strong) NSMutableData *dataBuffer;
And instantiate your buffer during viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.dataBuffer = [[NSMutableData alloc] init];
// do any other setup your class requires...
}
And then implement the delegate methods:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// clear the buffer in case it has been used previously
[self.dataBuffer setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.dataBuffer appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSString *response = [NSString stringWithUTF8String:[self.dataBuffer bytes]]);
NSLog(#"response from HTTP request=>%#", response);
}
This can all also be done using a third-party networking library like ASIHTTPRequest (which is no longer under active develoment) or AFNetworking, but sometimes those can be overkill depending upon what you are trying to accomplish
Implement the delegate method for the NSURLConnection,
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response.
This method is called by the app when the request finishes. You can access response data using the 'response' parameter.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
Then just convert the 'data' parameter into a string using:
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
Then you can search the response for whatever string you want, e.g., "email incorrect".
PS: I generally don't use NSURLConnection/NSURLRequest for HTTP requests, I'd recommend you check out ASIHTTPRequest for really simple HTTP requests/connections.
Hope this helps.

Retrieving data from website to iphone

I was recently looking at an example from apple about NSURLConnection and I tried implementing it into my code but I am not sure if am I doing it right.
Basically I want the connection to go to my website where I have it connected to a php script that runs the search within my database and then echo's it to the browser. I want the iphone to take the line that is echoed and hold it into a string variable. This is my code.
Is this correctly done?
Thank you in advance
NSString *stringToBeSent= [[NSString alloc] initWithFormat:
#"http:/xxxxx/siteSql.php? data=%#",theData];
NSURLRequest *theRequest=[NSURLRequest requestWithURL:
[NSURL URLWithString:stringToBeSent]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc]
initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere... in my .h file
// NSMutableData *receivedData;
receivedData = [[NSMutableData data] retain];
//convert NSMutableData to a string
NSString *stringData= [[NSString alloc]
initWithData:receivedData encoding:NSUTF8StringEncoding];
NSLog (#"result%#", receivedData);
} else {
// Inform the user that the connection failed.
NSLog(#"failed");
}
I think you might be missing a couple things:
In the method that you use to trigger retrieving the data make sure you release the old data before initializing:
[retrievedData release];
retrievedData=[[NSMutableData alloc] init];
I assume that space is a typo or something for the URL?
You don't need to call requestWithURL:cachePolicy:timeoutInterval: requestWithURL: uses the same defaults as you chose.
The data will come in blocks. You've got to handle that over time, outside this method, using the delegate method connection:didReceiveData:, like so:
- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data
{
[receivedData appendData:data];
}
Similarly, if you want something done with the data once it's all received, you do it in connectionDidFinishLoading: NOTE THAT THE CONNECTION IS RELEASED so it has to be defined in your header as an instance variable (eg. NSURLConnection *connection;
- (void)connectionDidFinishLoading:(NSURLConnection *)conn
{
NSString *stringData= [[NSString alloc]
initWithData:receivedData encoding:NSUTF8StringEncoding];
NSLog(#"Got data? %#", stringData);
[connection release];
connection = nil;
// Do unbelievably cool stuff here //
}
Also look into the other delegate methods like connection:didFailWithError: You probably want to release the connection and stringData there as well, in case of an error.
I hope that's of some help! Enjoy!

NSURLConnection leak issues

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.