what's wrong with this simple code(objective c)? - objective-c

i want to download some webpages ,but this example code seems doesn't work.
it prints "begin download" and then exits,why the delegates method does not be executed?
what's wrong in the example code?
thanks
main.m
#import <Foundation/Foundation.h>
#import "Test.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
Test * test = [[Test alloc]init];
[test downloadData];
}
[NSThread sleepForTimeInterval:21.0f];
return 0;
}
Test.h
#import <Foundation/Foundation.h>
#interface Test : NSObject <NSURLConnectionDelegate,NSURLConnectionDataDelegate,NSURLConnectionDownloadDelegate>
#property (retain) NSMutableData * receivedData;
#property (retain) NSURLConnection * theConnection;
- (void) downloadData;
#end
Test.m
#import "Test.h"
#implementation Test
- (void) downloadData
{
NSURLRequest *theRequest=
[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sf.net/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
_receivedData = [NSMutableData dataWithCapacity: 0];
[NSURLConnection sendSynchronousRequest:theRequest
returningResponse:nil
error:nil];
NSLog(#"begin download");
if (!_theConnection) {
_receivedData = nil;
// Inform the user that the connection failed.
}
}
enter code here
#pragma mark -
#pragma mark NSURLConnectionDataDelegateenter code here methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"1");
[_receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"2");
[_receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
NSLog(#"3");
_theConnection = nil;
_receivedData = nil;
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"4");
NSLog(#"Succeeded! Received %lu bytes of data",(unsigned long)[_receivedData length]);
_theConnection = nil;
_receivedData = nil;
}
-(void) connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
{
NSLog(#"5");
}
#end

you have two ways to Synchronous or Asynchonous :
In Synchronous any delegates was not called and the right line is
https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/clm/NSURLConnection/sendSynchronousRequest:returningResponse:error:
_receivedData = [NSURLConnection sendSynchronousRequest:theRequest
returningResponse:nil
error:nil];
NSLog(#"begin download");
if (!_theConnection) {
_receivedData = nil;
// Inform the user that the connection failed.
}
In Asynchronous you need to use – initWithRequest:delegate:
https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLConnection/initWithRequest:delegate:
[NSURLConnection alloc] initWithRequest:delegate:theRequest
delegate:self];
NSLog(#"begin download");

Related

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.

GHUnit test with async HTTP get action

I want to test async HTTP get action.
#interface Sender : NSObject<NSURLConnectionDataDelegate, NSURLConnectionDelegate>
{
NSMutableData *buffer;
NSString *_html
}
- (void)getHtml;
#end
#implementation Sender
- (void)getHtml
{
NSString *urlstr = #"http://www.yahoo.co.jp";
NSURL *url = [NSURL URLWithString:urlstr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
if (conn) {
buffer = [NSMutableData data];
} else {
// error handling
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[buffer appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeed!! Received %d bytes of data", [buffer length]);
_html = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
NSLog(#"%#", _html);
}
#end
I have to let _html property ?
#import "SenderTestCase.h"
#import "Sender.h"
#implementation SenderTestCase
- (void)setUpClass
{
sender = [[Sender alloc] init];
}
- (void)testGetHtml
{
[self prepare];
[sender getHtml];
[self performSelector:#selector(_succeedGetHtml) withObject:nil afterDelay:3.0];
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:4.0];
}
- (void)_succeedGetHtml
{
if (sender.html != nil) {
[self notify:kGHUnitWaitStatusSuccess forSelector:#selector(testGetHtml)];
};
}
#end
If there is more nice way, please tell me.
Thank you for your kindness.
You are doing it correct.
If you want your code to be nicer (and shorter), consider using AFNetworking and the use of blocks.
I have a GHUnit async test example, which shows nicely within 1 method.

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.

Using NSURLConnection async connection, how to get the received data in main function?

some codes in httpController.h like this:
#interface httpController:NSObject{
...
NSMutableData *receivedData;
}
#property (nonatomic,retain) NSMutableData *receivedData;
and some codes in httpController.m file like this:
#implementation httpController
#synthesize receivedData;
...
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (!receivedData) {
receivedData = [[NSMutableData alloc] init];
}
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
}
Then I want using the receivedData in the main.m file, like this:
int main(int argc, const char *argv[])
{
HttpController *httpController = [[HttpController alloc] init];
NSURLRequest *request = ...;
NSURLConnection *connetion = ...;
if(connection)
{
NSMutableData *_receviedData = httpController.receivedData;
NSString * dataString = [[[NSString alloc] initWithData:_receviedData encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"%#",dataString);
}
[[NSRunLoop currentRunLoop] run];
}
But i found that in the main() function, the value of _receivedData is empty, and there is noting outputted. Anyone can tell me What's wrong about it?
+connectionWithRequest:delegate: runs asynchronously. It looks like it's not finishing the connection before returning, which is why you don't see any data. Try +sendSynchronousRequest:returningResponse:error: instead, as this will block the thread until the connection finishes.
There's no need for a HttpController/delegate when using +sendSynchronousRequest:returningResponse:error: either. Here's how to do it:
int main(int argc, const char *argv[])
{
NSURL *url = [NSURL URLWithString:#"http://www.yahoo.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response = nil;
NSError *error = nil;
// This blocks "this" thread until it's done.
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!data)
{
NSLog(#"Error: %#", error);
}
else
{
NSString *dataString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"%#", dataString);
}
}
If you don't want to block the thread, then +connectionWithRequest:delegate: is the way to go. But you'll have to write your code differently, and should read the docs.

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