Is there any way that I can have a method sleep until a notification is posted? This is for an asynchronous NSURLConnection. I cannot move to a synchronous connection for multiple reasons.
Methods cannot "sleep"; that only applies to threads. Just split the code that needs to wait out into another method and have that method called when the notification arrives.
- (void) doStuffBeforeConnection {
[self doPreConnectionStuff];
NSURL * url = [NSURL URLWithString:#"/U/R/L"];
NSURLRequest * request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:0];
NSURLConnection * conn = [NSURLConnection connectionWithRequest:request
delegate:self];
return;
// We are now "waiting"...
}
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self nowDoStuffThatNeededToWait:response];
}
use addobserver and set a target class with selector to be fired on notification.
when you need to trigger, use postNotification with notificatonName.
there you go..!!
Related
I am relatively new to objective-c but struggling with delegates when it comes to NSURLConnection. Below I have an implementation file api.m
Elsewhere in my viewcontrollers I call this api object with the method getGroups and the purpose here is to return the number of groups found when the API request is made. I can see the data in the didReceiveData but how can I get this data back into my getGroups so that I can access it in my viewController?
In my view controller I have something like:
NSInteger *numGroups = [apiRequest getGroups];
and in my api.m implementation file I have the following. Again everything works I am just not sure how to return the data from didReceiveData back so I can access it in getGroups method.
#import "API.h"
#import "Constants.h"
#import "JSONParser.h"
#implementation API
#synthesize user, url, receivedData
-(NSInteger)getGroups {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
[request setValue:APIKEY forHTTPHeaderField:#"apikey"];
[request setHTTPMethod:#"GET"];
[request setURL:url];
NSURLConnection *myConnection;
myConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//How do I access what was append'd in receivedData below
return 2;
}
/* ----------------------------------------------------------------------------------------
NSURLConnection Delegates
------------------------------------------------------------------------------------------- */
// Check the response code that was returned
- (NSInteger)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return [httpResponse statusCode];
}
// Take a peak at the data returned.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"DATA: %#", [data description]);
//How to get this information back up into the getGroups method
[receivedData appendData: data];
}
// Close the connection
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
NSLog(#"Connection Closed.");
}
#end
What you want to do is in your ViewController that is calling the API set the API's delegate to self. Then you need to add those delegate methods inside your ViewController, not use them out of the API. That way when the NSURLConnection tries to call one of the delegate methods it will be accessible within youre ViewController. You also want to make sure you add the delegate protocol inside your ViewController's .h file as well.
As a quick example your VC.h file will contain the following:
#interface ViewController : UIViewController <NSURLConnectionDataDelegate>
Then in your VC.m file you'd have the following methods:
/* ----------------------------------------------------------------------------------------
NSURLConnection Delegates
------------------------------------------------------------------------------------------- */
// Check the response code that was returned
- (NSInteger)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return [httpResponse statusCode];
}
// Take a peak at the data returned.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"DATA: %#", [data description]);
//How to get this information back up into the getGroups method
[receivedData appendData: data];
}
// Close the connection
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
NSLog(#"Connection Closed.");
}
Now when your NSURLConnection tries to call didReceiveData it will be called inside your ViewController, not in the API.
As a side note I whole heartedly recommend taking #SK9's advice and make this an Async call to abstract it from the main thread.
NSURLConnection's sendSynchronousRequest:returningResponse:error:, see here, will return a data object. Do be sure you're happy to block the current thread like this. I'd prefer for this to be an asynchronous request, with a completion block to handle the return. More details on the page I referred to, but do make reading up on blocks a priority if this is new. The Short Practical Guid to Blocks might help.
The UIWebView does not automatically support processing of Passbook .pkpass files.
In this technical note, Apple recommend implementing a check via the UIWebViewDelegate methods to sniff out the MIME type and process it accordingly.
To add passes using a UIWebView, implement the appropriate
UIWebViewDelegate methods to identify when the view loads data with a
MIME type of application/vnd.apple.pkpass
However, I cannot find anything within the UIWebView Delegate Protocol Reference that is capable of providing the MIME type.
I can successfully download and process files directly using an NSURLConnection delegate with no problem, but what I wish to achieve is for passes to be properly processed if a user clicks on an Add To Passbook button while browsing within a UIWebView. Since I do not know the link, and many providers do not suffix their links with a .pkpass extension, following Apple's advice of examining the MIME type seems the best way to go.
I have tried adding the following
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)newRequest
navigationType:(UIWebViewNavigationType)navigationType
{
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[newRequest URL]];
// Spoof iOS Safari headers for sites that sniff the User Agent
[req addValue:#"Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25" forHTTPHeaderField:#"User-Agent"];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:newRequest delegate:self];
return YES;
}
My NSURLConnection delegate:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSString *mime = [response MIMEType];
if ([mime isEqualToString:#"application/vnd.apple.pkpass"] && ![_data length]) {
_data = nil; // clear any old data
_data = [[NSMutableData alloc] init];
[_webPanel stopLoading];
}
}
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
[_data appendData:data];
NSLog(#"Size: %d", [_data length]);
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
if ([_data length]) {
PKAddPassesViewController *pkvc = [PassKitAPI presentPKPassFileFromData:_data];
pkvc.delegate = self;
[self presentViewController:pkvc
animated:YES
completion:nil];
}
}
The NSURLConnection delegates work fine when a connection is invoked directly, without the UIWebView. However, when I try launching an NSURLConnection from the UIWebView delegate the pass download fails because the only 80% or so of the .pkpass is being downloaded (I get a random mismatch of bytes in the _data variable and the Content-Length header).
So, my questions:
Is there an easier way to get hold of a MIME type, directly from the UIWebView Delegate methods?
If not, then am I going about this the right way with opening up a parallel NSURLConnection, or is there a better way?
If an NSURLConnection is the way to go, then what could be causing it to stop short of downloading the full file?
Just use js
let contentType = webView.stringByEvaluatingJavaScript(from: "document.contentType;")
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:req delegate:self];
[conn start];
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSString *mime = [response MIMEType];
NSLog(#"%#",mime);
}
You could try subclassing NSURLProtocol and handling the response information parsing there.
Look at
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy
Don't forget to about subresources also using these hooks.
I'm working on a project for school, and I was wondering if there was a way to actually update the connection with NSURLConnection if a user submits something. For example, I'm having twitter search based on username, and I have a default username set up when the view loads. What would I have to do to refresh that connection when a user enters a different username on that page? I have my button handlers all set up, and I'm using the same code as in my viewDidLoad function. Is there a method call or something that will actually reset the connection?
Thanks,
David
I attached example codes
// member variable 'conn'
// NSURLConnection *conn;
// #property(strong, nonatomic) NSURLConnection *conn;
-(void)viewDidLoad {
[super viewDidLoad];
[self sendRequest];
}
- (IBAction)sendButtonClicked {
[self sendRequest];
}
- (void)sendRequest {
if (self.conn) {
[self.conn cancel];
self.conn = nil;
}
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
self.conn = conn;
[conn start];
}
// NSURLConnection Delegates
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// append received data
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// error handling
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// using data
}
I don't think you can "reset" the connection. You will have to make a new request. In your button clicked method simply make a new request call with the new user name.
It works like a request in a browser, when you change some parameter in the url the browser sends of a new request to the web-server.
have a look at this example by apple (if you dont need the caching part just focus on URLCacheConnection.h/.m) http://developer.apple.com/library/ios/#samplecode/URLCache/Listings/Classes_URLCacheController_m.html#//apple_ref/doc/uid/DTS40008061-Classes_URLCacheController_m-DontLinkElementID_10
i
I've read through tons of messages saying the same thing all over again : when you use a NSURLConnection, delegate methods are not called. I understand that Apple's doc are incomplete and reference deprecated methods, which is a shame, but I can't seem to find a solution.
Code for the request is there :
// Create request
NSURL *urlObj = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlObj cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
if (![NSURLConnection canHandleRequest:request]) {
NSLog(#"Can't handle request...");
return;
}
// Start connection
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; // Edited
});
...and code for the delegate methods is here :
- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Receiving response: %#, status %d", [(NSHTTPURLResponse*)response allHeaderFields], [(NSHTTPURLResponse*) response statusCode]);
self.data = [NSMutableData data];
}
- (void) connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error {
NSLog(#"Connection failed: %#", error);
[self _finish];
}
- (void) connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data {
[data appendData:_data];
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)_connection destinationURL:(NSURL *) destinationURL {
NSLog(#"Connection done!");
[self _finish];
}
There's not a lot of error checking here, but I've made sure of a few things :
Whatever happens, didReceiveData is never called, so I don't get any data
...but the data is transfered (I checked using tcpdump)
...and the other methods are called successfully.
If I use the NSURLConnectionDownloadDelegate instead of NSURLConnectionDataDelegate, everything works but I can't get a hold on the downloaded file (this is a known bug)
The request is not deallocated before completion by bad memory management
Nothing changes if I use a standard HTML page somewhere on the internet as my URL
The request is kicked off from the main queue
I don't want to use a third-party library, as, ultimately, these requests are to be included in a library of my own, and I'd like to minimize the dependencies. If I have to, I'll use CFNetwork directly, but it will be a huge pain in the you-know-what.
If you have any idea, it would help greatly. Thanks!
I ran into the same problem. Very annoying, but it seems that if you implement this method:
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
Then connection:didReceiveData: will never be called. You have to use connectionDidFinishLoading: instead... Yes, the docs say it is deprecated, but I think thats only because this method moved from NSURLConnectionDelegate into NSURLConnectionDataDelegate.
I like to use the sendAsynchronousRequest method.. there's less information during the connection, but the code is a lot cleaner.
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (data){
//do something with data
}
else if (error)
NSLog(#"%#",error);
}];
From Apple:
By default, a connection is scheduled on the current thread in the
default mode when it is created. If you create a connection with the
initWithRequest:delegate:startImmediately: method and provide NO for
the startImmediately parameter, you can schedule the connection on a
different run loop or mode before starting it with the start method.
You can schedule a connection on multiple run loops and modes, or on
the same run loop in multiple modes.
Unless there is a reason to explicitly run it in [NSRunLoop currentRunLoop],
you can remove these two lines:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
or change the mode to NSDefaultRunLoopMode
NSURLConnection API says " ..delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object."
Because dispatch_async will start new thread, and NSURLConnection will not pass to that other threat the call backs, so do not use dispatch_async with NSURLConnection.
You do not have to afraid about frozen user interface, NSURLConnection providing only the controls of asynchronous loads.
If you have more files to download, you can start some of connection in first turn, and later they finished, in the connectionDidFinishLoading: method you can start new connections.
int i=0;
for (RetrieveOneDocument *doc in self.documents) {
if (i<5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
i++;
}
}
..
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
ii++;
if(ii == 5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
ii=0;
}
}
One possible reason is that the outgoing NSURLRequest has been setup to have a -HTTPMethod of HEAD. Quite hard to do that by accident though!
I have a login method. Inside the method I use NSURLConnection to login and I would like the return the NSData response. The problem is that I return the NSData before the connection actually gets the data.
- (NSData*)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[responseData appendData:data]; //responseData is a global variable
NSLog(#"\nData is: %#", [[[NSString alloc] initWithData:responseData
encoding:NSUTF8StringEncoding]autorelease]);//this works
isLoaded = YES; //isLoaded is a BOOL
}
- (NSData*)login:(NSString*)username withPwd:(NSString*)password{
isLoaded = NO;
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if(connection){
NSLog(#"Connected");
}
while(isLoaded = NO){
[NSThread NSSleepForTimeInterval: 1];
}
isLoaded = NO;
return responseData;
}
The program gets stuck at the while loop, but without the while loop the program can retrieve the data from the server, it is just that the method seems to return responseData, before the delegate method changes it.
So my question is how can I make it so the method will return the responseData only after the server is done with it ?
Unless otherwise specified, NSURLConnection loads a URL asynchronously. It uses deleegate callbacks to update the delegate about the progress of the URL download.
Specifically, Utilize NSURLConnection delegate's connectionDidFinishLoading: method. Your NSURLConnection object will call this once all the data has been loaded. It is within this method that you can return your data.
You can load the data synchronously but you may end up blocking UI.
Good luck!
You should refactor your code.
You are using a asynchronous call (good), but you try to handle it synchronously (not so good — if not using a separate thread).
to use an asynchronous behavior, you need a callback, in cocoa-fashion this is usually a delegate method (or could be a block for newer code). Actually it is your connection:didReceiveData. this method will work with the returned data — not the one, where you started the request. therefor usually methods, that start a asynchronous request, do not return anything — and for sure not, what is expected to be return form the request.
- (void)login:(NSString*)username withPwd:(NSString*)password
{
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
//Note: you cannot change the delegate method signatures, as you did (your's returns an NSData object)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.responseData appendData:data]
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//Now that the connection was successfully terminated, do the real work.
}
look at this apple example code.
You can use the sync request method
- (NSData*)login:(NSString*)username withPwd:(NSString*)password
{
NSError *error = nil;
NSURLResponse *response = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]
return responseDate;
}
Documentation:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/clm/NSURLConnection/sendSynchronousRequest:returningResponse:error: