So I have some code like so:
#interface RequestHandler()
#property (nonatomic) NSInteger statusCode;
#end
#implementation RequestHandler
- (bool)sendRequest:(NSString *)surveyorId withData:(NSData *)requestData
{
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:true];
if (self.statusCode == 200)
{
return YES;
}
return NO;
}
Clearly the routine will carry on into the if-else statement before the request has finished. Therefore, self.statusCode is not set properly in the delegate didReceiveResponse before it is checked. What would be the best way of doing this?
I am just thinking of adding another bool property that will be set in connectionDidFinishLoading and then loop until this property is set. Once it has done that, then it will check self.statusCode. However I am thinking this will block the thread will it not? It will be no different from a sendSynchronousRequest right? Is there any way to do this without putting it into a background thread?
Instead of your sendRequest:withData: method returning a BOOL indicating success/failure, it would be better for your RequestHandler to have a delegate. It could then let its delegate know about the success/failure/whatever else when the asynchronous request has finished, instead of trying to return this information from the sendRequest:withData: method (which, as you've found out, doesn't work so well).
So, you could define you delegate protocol something like this (just as an example - you might want to include some more information in these):
#protocol RequestHandlerDelegate <NSObject>
- (void)requestHandlerSuccessfullyCompletedRequest:(RequestHandler *)sender;
- (void)requestHandlerFailedToCompletedRequest:(RequestHandler *)sender;
#end
Then, give your RequestHandler a delegate property of something that conforms to this protocol:
#property (nonatomic, weak) id<RequestHandlerDelegate> delegate;
(Make sure you set something as the delegate!)
Then, when your asynchronous request completes, you can send your delegate the appropriate message, e.g.:
[self.delegate requestHandlerSuccessfullyCompletedRequest:self];
You'll need to implement the NSURLConnection delegate methods in RequestHandler (from your code, I assume you've already done that), or, if your are targeting iOS 7+, you could take a look at NSURLSession instead.
You have to implement 2 delegate methods:
Status code: - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
Received data: - (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
Example usage:
Declaration
#interface RequestHandler : NSObject <NSURLConnectionDelegate>
{
NSMutableData *receivedData;
}
Request
- (void)sendRequest:(NSString *)surveyorId withData:(NSData *)requestData
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
// Apply params in http body
if (requestData) {
[request setHTTPBody:requestData];
}
[request setURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
}
Delegates
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *responseCode = (NSHTTPURLResponse *)response;
if ([self.delegate respondsToSelector:#selector(didReceiveResponseCode:)]) {
[self.delegate didReceiveResponseCode:responseCode];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
receivedData = [[NSMutableData alloc] initWithData:data];
if ([self.delegate respondsToSelector:#selector(connectionSucceedWithData:)]) {
[self.delegate connectionSucceedWithData:receivedData];
}
}
Instead of using NSURLConnection with delegate methods you can use NSURLConnection sendAsynchronousRequest block in your code. In the example you can check connection error and compare status codes.
NSURL *URL = [NSURL URLWithString:#"http://yourURLHere.com"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *rspreportStatus, NSData *datareportStatus, NSError *e)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)rspreportStatus;
int code = [httpResponse statusCode];
if (e == nil && code == 200)
{
// SUCCESS
} else {
// NOT SUCCESS
}
}];
You can also check by logging this returnString.
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
NSArray *arrpicResult = [returnString JSONValue];
Related
I want to implement file downloading with progress from my server.
I my code I'm using a custom class which is delegated by
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://example.com"]];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"6.0")) {
DownloadCallback *dc = [[DownloadCallback alloc] initWithCallbackProgress:^(long long res){
NSLog(#"%lld", res);
} withCallbackReady:^(long long res){
NSLog(#"READY %lld", res);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
} withCallbackError:^(NSError * error) {
NSLog(#"READY %#", error.domain);
}];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:dc];
// [connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];
header:
#interface DownloadCallback: NSObject<NSURLConnectionDataDelegate>{
#private void (^_progressHandler)(long long someParameter);
#private void (^_readyHandler)(long long someParameter);
#private void (^_errorHandler)(NSError *someParameter);
}
-(id) initWithCallbackProgress:(void(^)(long long))handler withCallbackReady:(void(^)(long long))handlerReady withCallbackError:(void(^)(NSError*))handlerError;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
#end
body:
#implementation DownloadCallback
-(id) initWithCallbackProgress:(void(^)(long long))handler withCallbackReady:(void(^)(long long))handlerReady withCallbackError:(void(^)(NSError*))handlerError{
self = [super init];
if (self) {
_progressHandler = [handler copy];
_readyHandler = [handlerReady copy];
_errorHandler = [handlerError copy];
}
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// self.expectedTotalSize = response.expectedContentLength;
// Call completion handler.
if (_readyHandler != nil)
_readyHandler(response.expectedContentLength);
// Clean up.
// [_completionHandler release];
_readyHandler = nil;
_progressHandler = nil;
_errorHandler = nil;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// self.recievedData += data.length;
if (_progressHandler != nil)
_progressHandler(data.length);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (_errorHandler != nil)
_errorHandler(error);
}
#end
But the callback events are not fired! At all!
The simple synch code work prefectly:
// Send a synchronous request
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://example.com"]];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil) {
// Parse data here
}
But I need a callback! How to resolve it? I've not found in stackoverflow a solution.
Futhermore, if I'm using a simple delegate to major class instead of DownloadCallback the same: the connection callbacks are not fired too.
Add the dealloc method to your callback class and out a breakpoint or log statement in it. See if it is deallocated before the callbacks are called.
If this is the case, your callback class instance is destroyed too soon. Make it a property of a class that will for sure live longer then the request.
Also, you should make sure that this code:
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:dc];
[connection start];
is called on a thread outlives the connection and has a runloop. The easiest way to achieve this is to call that code on the main-queue. Your code-example does not show on which queue that is called. If it is not working I assume it is because your calling it on a background queue.
You can dispatch to a background queue from the delegate callbacks of you want/need to.
As a sidenote, if you are building something new, you should try and use NSURLSession instead of NSURLConnection. NSURLSession is more secure, easier to use and not deprecated. NSURLConnection is deprecated.
I am developing a framework which requires error callbacks. I have used delegate methods to give error callbacks to the users(developer) of the framework. Can I use NSError to indicate error details in the delegate method or simply use NSDictionary instead ? Are there any memory issues concerned here?
Instead of delegate methods, you should use blocks with proper error handling, something like this:
Interface:
typedef void(^ResultBlock)(id result, NSError *error);
extern NSString * const DummyErrorDomain;
enum{
DummyErrorReason1,
DummyErrorReason2
};
#interface Dummy : NSObject
-(void)doSomething:(ResultBlock)resultBlock;
#end
Implementation with async NSURLConnection:
NSString * const DummyErrorDomain = #"DummyErrorDomain";
#interface Dummy ()
#property (strong) ResultBlock resultBlock;
#property NSURLConnection *connection;
#property NSMutableData *responseData;
#end
#implementation Dummy
-(void)doSomething:(ResultBlock)resultBlock
{
self.resultBlock = resultBlock;
self.responseData = [[NSMutableData alloc] init];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"dummyURL"]];
self.connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
self.resultBlock(nil,error);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
self.resultBlock(self.responseData, nil);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.responseData appendData:data];
}
#end
Usage:
-(void)usage
{
Dummy *dummy = [[Dummy alloc] init];
[dummy doSomething:^(id result, NSError *error) {
if(result){
// everything went fine
}else{
// error handling
}
}];
}
I created a class customDownload with the following methods:
-(NSString *) getTextFromLink: (PreliteRequest *) requestDetails
asyncConnection: (BOOL) isAsync
callbackMethod: (SEL) methodToExecute {
mainRequest = requestDetails;
NSMutableURLRequest *postRequest = [[NSMutableURLRequest alloc] init];
NSURLRequest *getRequest = [[NSURLRequest alloc] init];
NSURLConnection *connection;
NSURLResponse * response = nil;
NSError * error = nil;
if ([[requestDetails getType] isEqualToString:#"POST"]) {
[postRequest setURL:[NSURL URLWithString:[requestDetails getUrl]]];
[postRequest setHTTPMethod:[requestDetails getType]];
[postRequest setValue:[requestDetails getPostLenght] forHTTPHeaderField:#"Content-Length"];
[postRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[postRequest setHTTPBody:[requestDetails getPostParameters]];
if (isAsync) {
tmpMethod = methodToExecute;
connection = [[NSURLConnection alloc] initWithRequest:postRequest delegate:self];
} else
downloadedData = (NSMutableData *)[NSURLConnection sendSynchronousRequest:postRequest returningResponse:&response error:&error];
} else {
getRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#%#",[requestDetails getUrl],[requestDetails getGetParameters]]]];
if (isAsync) {
tmpMethod = methodToExecute;
connection = [[NSURLConnection alloc] initWithRequest:getRequest delegate:self];
} else
downloadedData = (NSMutableData *)[NSURLConnection sendSynchronousRequest:getRequest returningResponse:&response error:&error];
}
NSString *result=[[NSString alloc]initWithData:downloadedData encoding:NSUTF8StringEncoding];
return result;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
downloadedData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append the new data to the instance variable you declared
[downloadedData appendData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
return nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSString *tmpResult = [[NSString alloc]initWithData:downloadedData encoding:NSUTF8StringEncoding];
[self performSelector:tmpMethod withObject:tmpResult];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSLog(#"Connection error: %#",error);
}
In my view controller I declare the previous class and call the only method of that class getTextFromLink.
download = [[customDownload alloc] init];
[download getTextFromLink:request asyncConnection:YES callbackMethod:tmpSelector];
SEL tmpSelector = #selector(printResult:);
-(void) printResult:(NSString *) resultToPrint {
NSLog(#"Risultato: %#",resultToPrint);
}
I pass to getTextFromLink the tmpSelector as parameter because that is the method I would like to call as soon the getTextFromDownloadLink has finished its job.
Actually getTextFromLink execute an asynchronous connection.
What I'm trying to do is to execute something when the asyncronous connection finished to download datas.
I would like to create a callback custom class to do this.
Can anyone help me?
Rather than this selector model, generally people would use blocks for this. For example, define a typedef for your block:
typedef void(^PreliteRequestCompletionHandler)(NSString *string);
Since you're dealing with an asynchronous pattern, you might want to define a property which you can use to save this completion handler to call later:
#property (nonatomic, copy) PreliteRequestCompletionHandler completionHandler;
You can then change that selector parameter to be a block parameter:
-(NSString *) getTextFromLink: (PreliteRequest *) requestDetails
asyncConnection: (BOOL) isAsync
completionHandler: (PreliteRequestCompletionHandler)completionHandler {
self.completionHandler = completionHandler;
// do stuff
}
And then, when you want to call that completion block, you do something like:
NSString *result = ...;
if (self.completionHandler) {
self.completionHandler(result);
}
And then you can now use this new block parameter to your method:
download = [[customDownload alloc] init];
[download getTextFromLink:request asyncConnection:YES completionHandler:^(NSString *result) {
NSLog(#"Risultato: %#", result);
}];
I have a little problem with my app. I want to send some http request asynchronously to server. I create this method:
- (void)sendHTTPRequest:(NSString *)urlString type:(NSString *)type idNegozio:(NSNumber *)idNegozio {
self.negozi = [[NSMutableArray alloc] init];
NSData *jsonData;
NSString *jsonString;
if ([type isEqualToString:#"shops"]) {
self.reqNeg = YES;
self.reqApp = NO;
...
jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:0 error:nil];
jsonString = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];
else if ([type isEqualToString:#"appointments"])
{
[self.loadingIconApp startAnimating];
self.reqNeg = NO;
self.reqApp = YES;
...
jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:0 error:nil];
jsonString = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *requestString = [NSString stringWithFormat:urlString];
NSURL *url = [NSURL URLWithString:requestString];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody: jsonData];
NSURLConnection * conn = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[conn start];
}
and I use this methods for connection:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.responseData appendData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
return nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (self.reqNeg == YES) {
//here use the responseData for my first http request
}
if (self.reqApp == YES) {
//here use the responseData for second http request
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
}
but in this way only the first connection works and I can use the responseData. While, If I try to send other http request the method connectionDidFinishLoading doesn't work and other methods too.
Anyone have an idea??
If you want to use the async request one by one you can do that:
- (void)request1 {
NSString *requestString = #"your url here";
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc]initWithURL:[NSURL URLWithString: requestString]]
queue:queue
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (!error && httpResponse.statusCode >= 200 && httpResponse.statusCode <300) {
// call the request2 here which is similar to request 1
// your request2 method here
}
}];
}
hope this help you~ thank you~
Your code looks good to me. Here are my ideas:
Are you sure your second NSURLConnection is being created and sent out?
Maybe it's never being sent.
Are you calling your sendHTTPRequest:type:idNegozio: method with a different type while your second connection is still sent out?
You don't have a check at the beginning of the send function to make sure you're not already sending out a connection. Maybe your flags are being switched mid-connection.
The if statements in your didFinish method should probably be combined with an else. Just in case you wanted to fire off an 'app' connection after handling a 'neg' connection you don't accidentally fall through and try to handle the response twice.
Also, you don't have to explicitly call 'start' on an NSURLConnection unless you pass NO to the startImmediately: parameter in the constructor. That shouldn't cause a problem though.
I am looking for a sample to send and receive http GET request in iOS. All I want to
do is handle communication in background thread such that it does not block main thread
and also want to handle http standard error code. Can anyone suggest me reference code or
example to handle http response data and handle proper memory management?
Any help will be thankful.
Two methods to achieve it:
1) NSURLCOnnection sendAsynchronousRequest method:
NSString *strURL= [NSString stringWithFormat:#"http://www.google.com/"];
NSURL *URL = [NSURL URLWithString:[strURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *requestURL = [[NSURLRequest alloc] initWithURL:URL];
[NSURLConnection sendAsynchronousRequest:requestURL
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Response is:%#",[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
}];
2) Create and fire request then NSURLConnection Delegate Methods to get the response:
// Create the request.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com"]];
// Create url connection and fire request
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
#pragma mark NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// A response has been received, this is where we initialize the instance var you created
// so that we can append data to it in the didReceiveData method
// Furthermore, this method is called each time there is a redirect so reinitializing it
// also serves to clear it
_responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append the new data to the instance variable you declared
[_responseData appendData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// The request has failed for some reason!
// Check the error var
}