Application crash when using sendAsynchronously with RKRequest - objective-c

I want to use Restkit to download a file asynchronously. However the application crash when sendAsynchronously method was used. If I used sendSynchronously instead of sendAsynchronously, it didn't crash. Is there anybody know why?
Here is my code
NSString *passURL = #"https://s3.amazonaws.com/mheydt-mhtnd/2012-03-13-1.png";
RKRequest *request = [RKRequest requestWithURL:[NSURL URLWithString: passURL]];
request.delegate = self;
[request sendAsynchronously];
- (void)request:(RKRequest *)request didReceiveResponse:(RKResponse *)response {
NSLog(#"didReceiveResponse");
}
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
NSLog(#"didLoadResponse");
}
Thank you very much!

When using sendAsynchronously in this way, the request is not retained. You would better use the send method which adds the request in an instance of RKRequestQueue (which prevents the request to be freed too soon):
[request send];
Or, you can strongly reference the request by yourself. Your code would become:
#interface MyClass
{
RKRequest *request;
}
We assume that this is a strong reference. And in the implementation, you would have:
#implementation MyClass
- (void)sendTheRequest
{
NSString *passURL = #"https://s3.amazonaws.com/mheydt-mhtnd/2012-03-13-1.png";
request = [RKRequest requestWithURL:[NSURL URLWithString: passURL]];
request.delegate = self;
[request sendAsynchronously];
}
- (void)request:(RKRequest *)request didReceiveResponse:(RKResponse *)response {
NSLog(#"didReceiveResponse");
request = nil;
}
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
NSLog(#"didLoadResponse");
request = nil;
}
- (void)request:(RKRequest *)request didFailLoadWithError:(NSError *)error {
NSLog(#"didFailLoadWithError");
request = nil;
}
#end

Related

NSURLConnection switch to NSURLSession returning 404 response

I am attempting to convert my project from using NSURLConnection to NSURLSession. However, after making the switch I cannot seem to get a response from the server. The returned response from the server is always 404.
My original code, using NSURLConnection:
#implementation RecorderManager
- (void)sendRequestToURL:(NSString*)url withData:(NSData*)postData forScreen:(NSString *)inScreenName
{
RecorderAsynchSubmit* delegate = [[RecorderAsynchSubmit alloc] initWithScreenName:inScreenName andURL:url];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:delegate];
}
#implementation RecorderAsynchSubmit
// Implementing NSURLConnectionDelegate, NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// Handle error
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[httpResponse setLength:0];
httpResponseCode = [((NSHTTPURLResponse *) response) statusCode];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)httpdata
{
[httpResponse appendData:httpdata];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *responseString = [[[NSString alloc] initWithData:httpResponse encoding:NSUTF8StringEncoding] copy];
if (httpResponseCode == STATUS_CODE_VALID) { // response code for entry submitted
responseString = #"Submit Succeeded";
} else {
responseString = [NSString stringWithFormat:#"Your entry could not be submitted. Data has been stored locally. Error Message: %#", responseString];
}
[[NSNotificationCenter defaultCenter]
postNotificationName:#"submissionCompleteEvent"
object:[[RecorderNotificationMessage alloc] initWithStatusMessage:responseString detailedMessage:responseString]];
}
My updated code using NSURLSessionTask:
#implementation RecorderManager
- (void)sendRequestToURL:(NSString*)url withData:(NSData*)postData forScreen:(NSString *)inScreenName
{
RecorderAsynchSubmit* delegate = [[RecorderAsynchSubmit alloc] initWithScreenName:inScreenName andURL:url];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil];
NSURL *URL = [NSURL URLWithString:url];
NSURLSessionTask *task = [session dataTaskWithURL:URL];
[task resume];
}
#implementation RecorderAsynchSubmit
// Implementing NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
[httpResponse setLength:0];
httpResponseCode = [((NSHTTPURLResponse *) response) statusCode];
completionHandler(NSURLSessionResponseAllow);
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
[httpResponse appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
if (error) {
// Handle error
} else {
NSString *responseString = [[[NSString alloc] initWithData:httpResponse encoding:NSUTF8StringEncoding] copy];
if (httpResponseCode == STATUS_CODE_VALID) {
responseString = #"Submit Succeeded";
} else {
responseString = [NSString stringWithFormat:#"Your entry could not be submitted. Data has been stored locally. Error Message: %#", responseString];
}
[[NSNotificationCenter defaultCenter]
postNotificationName:#"submissionCompleteEvent"
object:[[RecorderNotificationMessage alloc] initWithStatusMessage:responseString detailedMessage:responseString]];
}
}
The original code is working fine and returns the expected result, however the updated code always returns a 404 response from the server.

Retrieving images once saved to a server

I have code written that uploads an image to the server, but I am not sure how to retrieve the images after I upload them. I tried using an NSURL request, but the did receiveData delegate method is never called. Below I've included all relevant code related to uploading the picture, and then my attempt at pulling the data using an NSURL request. Is there anything conceptually that I'm doing wrong? Thank you.
- (IBAction)nextButtonPressed:(id)sender {
[self.signupController uploadProfilePicture:UIImagePNGRepresentation(self.imageView.image) completion:^(NSError *error){
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[SyncController sharedInstance] sync];
[self performSegueWithIdentifier:#"addFriendsSegue" sender:self];
}];
}];
}
And the uploadProfilePicture method:
- (void)uploadProfilePicture:(NSData *)imageData completion:(void (^)(NSError *error))completion {
BRUser *user = [BRSession userWithContext:[[BRCoreDataManager sharedManager] mainContext]];
[self.apiClient uploadProfilePicture:imageData forUser:user parameters:nil completion:[self uploadProfilePictureHandlerWithCompletion:completion]];
}
And then, also, here is the upload profile picture method in the API client:
- (void)uploadProfilePicture:(NSData *)imageData forUser:(BRUser *)user parameters:(NSDictionary *)parameters completion:(BRAPIClientCompletionBlock)completion {
NSError *error;
NSURLRequest *request = [self.requestSerializer multiformRequestForAPIAction:BRAPIActionCreate nestedResource:#"image" parent:user data:imageData parameters:parameters error:&error];
if (error) {
completion(nil, error);
}
NSURLSessionDataTaskCompletionBlock dataTaskCompletion = [self requestHandlerWithHTTPStatusErrors:#{ #400 : #(BRAPIUnauthorized) } completion:completion];
NSURLSessionDataTask *task = [self.authURLSession dataTaskWithRequest:request completionHandler:dataTaskCompletion];
[task resume];
}
and the multiform method called in the previous method:
- (NSURLRequest *)multiformRequestForAPIAction:(BRAPIAction)action nestedResource:(id)resource parent:(id)parent data:(NSData *)data parameters:(NSDictionary *)parameters error:(NSError *__autoreleasing *)error {
NSParameterAssert(action);
NSParameterAssert(resource);
NSParameterAssert(parent);
NSParameterAssert(data);
NSString *method = [BRAPIRequestSerializer HTTPMethodForAPIAction:action];
NSURL *url = [self.apiURL nestedURLForResource:resource parent:parent];
NSLog(#"URL: %#",[url absoluteStringWithTrailingSlash]);
return [self.serializer multipartFormRequestWithMethod:method
URLString:[url absoluteStringWithTrailingSlash]
parameters:parameters
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:data name:#"image" fileName:#"image.png" mimeType:#"image/png"];
}
error:error];
}
And my failed attempt at retrieving the data via the url it's stored at:
-(void) downloadImageFromURL :(NSString *)imageUrlString{
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:imageUrlString]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
NSData * receivedData = [[NSMutableData alloc] init];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (!theConnection) {
// Release the receivedData object.
receivedData = nil;
// Inform the user that the connection failed.
NSLog(#"connection falied");
} else {
NSLog(#"connection succesful");
};
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"data: %#",data);
}
I also made sure to include the NSURLConnectionDelegate. The didRecieveData method is never called.
Feel free to let me know if there's more code you need to see!

Objective C: SSL Pining

I have implement NSURLConnectionDataDelegate in my class and I have implemented the method:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
But when I make new NSURLConnection this method isn't called. Why? Where is the problem? The connections is an SSL connection.
I have look inside an example project downloaded by internet (I don't remember where) and this project work fine, but if I do the some step in my project don't work. I need any external library or to set something in the proj? You can suggest me a for-dummies guide?
Edit 1:
In the example I use:
NSURL *httpsURL = [NSURL URLWithString:#"https://secure.skabber.com/json/"];
NSURLRequest *request2 = [NSURLRequest requestWithURL:httpsURL cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:15.0f];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request2 delegate:self];
[connection start];
But I want to work with any kind of NSURLConnection.
Edit 2:
This is my complete code:
#interface ConnectionDelegate : NSObject<NSURLConnectionDataDelegate>
#property (strong, nonatomic) NSURLConnection *connection;
#property (strong, nonatomic) NSMutableData *responseData;
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
-(void)testHTTPRequest;
#end
#implementation ConnectionDelegate
-(void)testHTTPRequest
{
NSURL *httpsURL = [NSURL URLWithString:#"https://secure.skabber.com/json/"];
NSURLRequest *request = [NSURLRequest requestWithURL:httpsURL cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:15.0f];
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
[self.connection start];
if ([self isSSLPinning]) {
[self printMessage:#"Making pinned request"];
}
else {
[self printMessage:#"Making non-pinned request"];
}
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
NSData *skabberCertData = [self skabberCert];
if ([remoteCertificateData isEqualToData:skabberCertData] || [self isSSLPinning] == NO) {
if ([self isSSLPinning] || [remoteCertificateData isEqualToData:skabberCertData]) {
[self printMessage:#"The server's certificate is the valid secure.skabber.com certificate. Allowing the request."];
}
else {
[self printMessage:#"The server's certificate does not match secure.skabber.com. Continuing anyway."];
}
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
else {
[self printMessage:#"The server's certificate does not match secure.skabber.com. Canceling the request."];
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (self.responseData == nil) {
self.responseData = [NSMutableData dataWithData:data];
}
else {
[self.responseData appendData:data];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *response = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
[self printMessage:response];
self.responseData = nil;
}
- (NSData *)skabberCert
{
NSString *cerPath = [[NSBundle mainBundle] pathForResource:#"secure.skabber.com" ofType:#"cer"];
return [NSData dataWithContentsOfFile:cerPath];
}
- (void)printMessage:(NSString *)message
{
Log(#"%#",message);
}
- (BOOL)isSSLPinning
{
NSString *envValue = [[[NSProcessInfo processInfo] environment] objectForKey:#"SSL_PINNING"];
return [envValue boolValue];
}
#end
If i put a breakpoint on connection:willSendRequestForAuthenticationChallenge: the program don't enter in it.

How to return data directly which was loaded by NSURLConnection if delegate functions are needed?

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];
}

Get actual URL from TinyURL

I want the user to get the actual URL from the TinyURL or Tiny.cc services, or any other URL redirectors. So is it possible for me to get the actual long URLs from the short redirected URLs, without making a browswer application that runs in the background?
Thanks in advance.
Header:
#import "UntitledViewController.h"
#implementation UntitledViewController
- (id)init
{
self = [super init];
if (self)
{
NSURL *url = [NSURL URLWithString:#"http://tinyurl.com/a3cx"];
[self loadTinyURL:url];
}
return self;
}
- (void)loadTinyURL:(NSURL *)url
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (!connection)
NSLog(#"could not connect with: %#", url);
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
int statusCode = [httpResponse statusCode];
// http statuscodes between 300 & 400 is a redirect ...
if (response && statusCode >= 300 && statusCode < 400)
NSLog(#"redirecting to : %#", [request URL]);
return request;
}
#end
Implementation:
//
// UntitledViewController.h
// Untitled
//
// Created by tushar chutani on 11-04-19.
// Copyright 2011 Fleetwood park secondary . All rights reserved.
//
#import <UIKit/UIKit.h>
#interface UntitledViewController : UIViewController {
}
- (void)loadTinyURL:(NSURL *)url;
#end
The NSURLConnection has a call back connection:willSendRequest:redirectResponse:. At this point you can inspect the redirectResponse to see where you are going.
UPDATE:
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
//Make sure tinyurl is doing the redirection
if([[[redirectResponse URL] host] compare:#"tinyurl.com"
options:NSCaseInsensitiveSearch] == NSOrderedSame)
{
NSLog(#"Redirect Location: %#", [request URL]);
}
//call [connection cancel]; to cancel the redirect and stop receiving data
//return nil; to cancel redirect but continue receiving data
//return request; will continue the redirection as normal
return request;
}
Check out the following code:
#import "TinyURLHandler.h"
#interface TinyURLHandler (Private)
- (void)loadTinyURL:(NSURL *)url;
#end
#implementation TinyURLHandler
- (id)init
{
self = [super init];
if (self)
{
NSURL *url = [NSURL URLWithString:#"http://tinyurl.com/a3cx"];
[self loadTinyURL:url];
}
return self;
}
- (void)loadTinyURL:(NSURL *)url
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (!connection)
NSLog(#"could not connect with: %#", url);
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
int statusCode = [httpResponse statusCode];
NSLog(#"%d : %#", statusCode, [NSHTTPURLResponse
localizedStringForStatusCode:statusCode]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
NSLog(#"finished");
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
int statusCode = [httpResponse statusCode];
// http statuscodes between 300 & 400 is a redirect ...
if (response && statusCode >= 300 && statusCode < 400)
NSLog(#"redirecting to : %#", [request URL]);
return request;
}
#end
# openingsposter: I'm not 100% sure why you want to see the original project, I guess it's because you want to figure out what would be a good way to extract the final URL? Well, what I would suggest is you create a delegate and inform the caller once you got the final URL. If you need an example, I can add more source code ...