Objective-c simple polling - objective-c

I'm new to objective-c and I'm building an app that requires a background polling to a generic API to refresh some data on my user interface.
After several hours looking for an answer/example that fits my problem I came across some solutions like the following:
long polling in objective-C
polling an external server from an app when it is launched
Poll to TCP server every hour ios
http://blog.sortedbits.com/async-downloading-of-data/
but unfortunately none of them covers my scenario which is really basic:
I need to start a polling when viewDidLoad, let's say an infinite loop, and on every iteration, let's say every 10 seconds, to call an API and when I didReceiveData I want to log that data into the console with an NSLog, obviously this can't be done on the main thread.
What I really need is a very simple example on how to do that, and with simple I mean:
I can't implement long polling/push notifications for many reasons and not looking for a way to do it, lets' just assume I cannot.
I don't want to rely on fancy frameworks like LRResty, RESTKit, AFNetworking or anything else since I don't really need them and also I can't believe that there is no SDK bulletin that can cover this basic scenario.
I don't need anything else besides what I described, so no authentication, no parameters in my request, no response handling etc. (since I'm new to objective-c and stuff not strictly needed could just be more confusing to me...)
The solution I'm looking for could be something like this (using NSOperationQueue to run my loop into a separate thread):
- (void)viewDidLoad {
[super viewDidLoad];
//To run polling on a separate thread
operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(doPolling) object:nil];
[operationQueue addOperation:operation];
}
-(void)doPolling {
MyDao *myDao = [MyDao new];
while (true) {
[myDao callApi];
[NSThread sleepForTimeInterval:10];
}
}
// and in MyDao
-(void)callApi {
NSMutableString *url= [NSMutableString stringWithFormat:#"%#",#"http:www.example.it"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
request.HTTPMethod = #"GET";
self.conn= [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(self.conn){
[self.conn start];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData %s","yes");
}
but unfortunately as stated here: Asynchronous NSURLConnection with NSOperation looks like I cannot do that.
Please help, I refuse to believe that there isn't a simple straightforward solution for this basic scenario.

Related

Asynchronous network operations in iOS 5

Everywhere I look, I see people explicitly using queues or run loops to do network operations. Should I still do that in iOS 5, or should I use NSURLConnection sendAsynchronousRequest:queue:completionHandler: instead? Is this the preferred method of doing network operations in iOS >= 5?
I can't answer for others' preference, but I rolled my own < os5, and I strongly prefer the block operation. a) I'm never interested in intermediate results of the network operation, or the repetitive code to handle them, b) the block is retained, so I get fewer race conditions where some aspect of the delegate gets prematurely released, and c) I never get mixed up about what code is running when a particular operation finishes.
In short, it's a huge improvement in the NSURLConnection interface, IMO.
It depends. For simple things, the new block-based API makes life a lot easier. However, compared to implementing the NSURLConnectionDelegate methods, you lose some control.
For example, it's not possible to cancel connections that have been initiated with this method, or to handle the response (e.g. for parsing headers) before all data is downloaded.
You can do something similar to this with iOS 4 as well using GCD.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSData *data = [NSURLConnection sendSynchronousRequest:blah returningResponse:response error:blah];
//process response body here
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI Code here
self.textView.text = [[NSString alloc] initWithData:data];
});
});
The problem with this code and the code OP posted is that, once the connection is made, you cannot cancel them.
Canceling a request on viewDidDisappear goes a long way in improving the performance of your application. I talk extensively about this on the book iOS PTL
Second reason why you need a third party networking framework like MKNetworkKit or RestKit or the now defunct ASIHTTP is for authentication. Most web service require that you authenticate using a NSURLCredential (HTTP Basic or HTTP Digest or Windows NTLM or oAuth)
This alone will take a couple of days to do if you roll out your own code. Not that you shouldn't do it. But there is no need for one as all these third party frameworks are extensively used and the chances or bugs or performance issues in them is less compared to your own code.
I've written a blog post that compares several approaches including NSURLConnection, AFNetworking, and ReactiveCocoa.
ReactiveCocoa approach
If you want to get really fancy with asynchronous network calls, you can try out ReactiveCocoa. ReactiveCocoa allows you to define signals that represent each of your network requests. And it allows you to chain multiple dependent network calls.
#import <ReactiveCocoa/ReactiveCocoa.h>
// ...
-(RACSignal*)signalGetNetworkStep1 {
NSURL* url = ...;
return [RACSignal createSignal:^(RACDisposable *(id subscriber) {
NSURLRequest *request1 = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation1 = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
[subscriber sendNext:JSON];
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
[subscriber sendError:error];
}];
[operation1 start];
return nil;
}
}
-(void) yourMethod {
[[self signalGetNetworkStep1] subscribeNext:^(id *x) {
// perform your custom business logic
}];
}

How to handle different requests using connectionDidFinishLoading in the same delegate?

Whenever I do a curl call using the below code:
NSURL *url = [NSURL URLWithString:requestURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:30];
if (connectionInProgress) {
[connectionInProgress cancel];
}
connectionInProgress = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
connectionDidFinishLoading is my final destination where I can manipulate the response data and call my next methods to continue with the app . If I hard-code some specific tasks like
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
[parser setDelegate:self];
[parser parse];
[someLabel setText:parsedTextFromXMLData];
}
If I need to do another curl call to a different address, wouldn't someLabel setText always get re-set again? Is there a way to make this delegate function behave differently on each curl call? (btw, is connectionDidFinishLoading usually the right place to put the next step of codes?) If so then wouldn't it always get called again by the next curl call?
Have a look at this S.O. post for a recipe concerning NSURLConnection and multiple requests.The suggestion is doing something like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (connection == firstConnection) {
// do something
}
else if (connection == secondConnection) {
// do something else
}
}
EDIT: the idea here is that connectionDidFinishLoading is a method of your own delegate (so you write it). In the delegate, you store the address of each connection you create; then, when the connection comes back with the data, you tell which connection it is by comparing its address to the one you stored in the delegate. -END EDIT
Another option you have is using the ASIHTTPRequest framework, which offers a request-based (as opposed to connection-based) delegation mechanism, so each request has got a delegate object to handle the result; or, in other words, the delegate receives a reference to the request, so you can easily tell which request result you are handling.
ASIHTTPRequest offers a bunch of advantages over NSURLConnection. You can read about them in this S.O. post.
There're 2 options to do this:
you can implement a separate class, that will be responsible for handling NSURLConnection delegate stuff and create a separate instance for each request
you can use NSObject key-value methods on NSURLConnection instance for setting up some tag, that will be checked in connectionDidFinishLoading: method
For me, option 1 will be a better approach

Simultaneous NSURLDownloads

I have a Cocoa Mac application set up to download files to a specific folder using NSURLDownload. This works great with a single download at a time. However, if I attempt to start multiple downloads, all but the last will fail immediately.
Is there any way to use NSURLDownload for multiple simultaneous downloads? Or what would be a good way to queue up multiple URLs to be downloaded in order? Or is there a more appropriate way to accomplish this (NSURLConnection seemed possible but I was unsure if I could set the download location and filename as I can with NSURLDownload)?
Each NSURLDownload represents a single downloading instance. You're probably trying to reuse the same one multiple times. It's an inherently asynchronous system that already used background threads. Here's an example based on Apple's sample code:
- (void)startDownloadingURL:sender
{
// Create a couple requests.
NSURLRequest *requestOne = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLRequest *requestTwo = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://stackoverflow.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create two download instances
NSURLDownload *downloadOne = [[NSURLDownload alloc] initWithRequest:requestOne delegate:self];
NSURLDownload *downloadTwo = [[NSURLDownload alloc] initWithRequest:requestTwo delegate:self];
if (downloadOne) {
// Set the destination file.
[downloadOne setDestination:#"/tmp" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
if (downloadTwo) {
// Set the destination file.
[downloadTwo setDestination:#"/tmp" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the connection.
[download release];
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
NSLog(#"The download %# has finished.", download)
// Release the download connection.
[download release];
}
If you attempt to use the same NSURLDownload for both NSURLRequests, then it will kill the previous connection.
I'd second using NSOperation if your on 10.5+ or greater. You could just throw 1 operation onto a queue for each download. Or you could even just use sendSynchronous request and use it with NSOperationQUeue's addOperationWithBlock (10.6+) method and then in your block you are throwing onto the queue you can just use [[NSOperationQueue mainQueue] addOperationWithBlock:^{ when you are doe with the code you need to execute or just periodically need to refresh the UI on the main thread, like so...
[myQueue addOperationWithBlock:^{
//download stuff here...
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//update main thread UI
}];
}];
you would just need to do this for each download.
If you're targeting 10.5+, you should look at NSOperation. It should allow you to build a generic solution for a single download, and then use the built-in queue facilities to manage dependencies if you require certain operations finish downloading before others begin.
Keep in mind that these APIs often expect delegate methods involved to be run on the main thread, so you'll need ensure that occurs if you're working with asynchronous APIs that function via delegate methods. (You can do this pretty simply by using performSelectorOnMainThread: and friends)

What's wrong on following URLConnection?

See also:
Objective-C Asynchronous Web Request with Cookies
I spent a day writing this code and can anyone tell me what is wrong here?
WSHelper is inherited from NSObject, I even tried NSDocument and NSObjectController and everything..
-(void) loadUrl: (NSString*) urlStr{
url = [[NSURL alloc] initWithString:urlStr];
request = [NSURLRequest requestWithURL:url cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
if(connection)
{
receivedData = [[NSMutableData data] retain];
//[connection start];
}
else
{ display error etc... }
NSApplication * app = [NSApplication sharedApplication];
[app runModalForWindow: waitWindow];// <-- this is the problem...
}
-(void)connection: (NSURLConnection*)connection didReceiveData:(NSData*)data{
progressText = #"Receiving Data...";
[receivedData appendData:data];
}
-(void)connection: (NSURLConnection *)connection didFailWithError:(NSError *)error{
progressText = #"Error...";
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText:[error localizedDescription]];
[alert runModal];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
progressText = #"Done...";
pData = [[NSData alloc] initWithData:receivedData];
[self hideWindow];
}
The code just wont do anything, it doesnt progress at all. I even tried it with/without startImmediately:YES but no luck !!!, this is executed in main window so even the thread and its run loop is running successfully.
I tried calling synchronous request, and it is working correctly !! But I need async solution.
I have added CoreServices.Framework in project, is there anything more I should be adding to the project? any compiler settings? Or do i have to initialize anything before I can use NSURLConnection?
Any solution to run NSURLConnection on different thread on its own NSRunLoop, Objective-C and MAC Development has no sample code anywhere in documentation that makes everything so difficult to code.
I also met the same problem that didn't get the delegate method called when using NSURLConnection in a Modal Window.
after some investigation, following code resolve it.
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:requst delegate:self startImmediately:NO];
[conn scheduleRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
[conn start];
However, when connectionDidFinishLoading called, [NSApp stopModal] doesn't work, need call [NSApp abortModal] instead.
Firstly you're making starting the connection too complicated. Change to:
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]
Remove [connection start]. Now:
Is your app definitely running the run loop normally? NSURLConnection requires this to work.
Are you able to perform a synchronous load of the URL request?
In the debugger, can you see that url is what you expect it to be? What is it?
Is it possible that you're deallocating WSHelper before any delegate messages are received? NSURLConnection is asynchoronous after all.
One does not need to do anything special to use NSURLConnection, it's a straightforward part of the Foundation framework. No special compiler settings required. No initialization before use. Nothing. Please don't start blindly trying stuff like bringing in CoreServices.Framework.
As sending the request synchronously works, there must be something wrong with your handling of the asynchronous aspect. It could be:
The runloop is not running in NSDefaultRunLoopMode so the connection is unable to schedule itself.
Some other part of your code is calling -cancel on the connection before it has a chance to load.
You are managing to deallocate the connection before it has a chance to load.
Real problem
Ah, in fact I've just realised what's going on. You are calling:
-[NSApp runModalForWindow:]
Read the description of what this method does. It's not running the run loop like NSURLConnection expects. I'd say that really, you don't want to be presenting a window quite like this while running a URL connection for it.
I'd also suggest that you implement the -connection:didReceiveResponse: delegate method too. You want to check here that the server is returning the expected status code.
You say that you're using this in a modal dialog? A modal dialog puts the run loop into a different mode. You should be able to get this to work by scheduling it to run in the modal dialog run loop mode, in addition to the normal run loop mode. Try adding this line of code after you allocate connection in loadURL:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSModalPanelRunLoopMode];
Hope that helps.
How do you know it isn't doing anything? Are there any error or warning messages during the compile? Are any error messages showing up on console when the program is running?
Have you tries setting breakpoints in your code and following through what you expect to be happening?

Cocoa: NSURLConnection not attempting an HTTP Request

I've had significant success with NSURL, NSURL[Mutable]Request, NSURLConnection with my iPhone applications. When trying to compile a stand alone Cocoa application, 10 line program to make a simple HTTP request, there are zero compiler errors or warnings. The program compiles fine, yet the HTTP Request is never made to my web server (I'm running a tcpdump and watching Apache logs in parallel). When I run very similar code in an iPhone app, essentially copy/pasted as evil as that is, all works golden.
I kept the code for the 'obj' declaration in the delegate to NSURLConnection out of this code snippet for the sake of simplicity. I'm also passing the following to gcc:
gcc -o foo foo.m -lobjc -framework cocoa
Thanks for any insight.
#import <Cocoa/Cocoa.h>
int main (int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString * urlstr = #"http://tmp/test.php";
[NSApplication sharedApplication];
NSObject *obj = [[NSObject alloc] init];
NSURL *url = [NSURL URLWithString: urlstr];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
if([request isKindOfClass:[NSMutableURLRequest class]])
NSLog(#"request is of type NSMutableURLRequest");
[request setHTTPMethod:#"GET"];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];
NSURLConnection *connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:obj
startImmediately:YES];
if(connection)
NSLog(#"We do have a connection.");
[pool release];
return 0;
}
The other poster pretty much answered this for you, but I thought I would just add a few things.
First, you don't really need to link to Cocoa for this, just linking to the Foundation framework is okay. Also, since you don't need a connection to the Window Server, you can get rid of the [NSApplication sharedApplicaiton] call. If you want just a simple, console test application to start with, use what you have now and add this before your [pool realease] call:
[[NSRunLoop currentRunLoop] run];
Please note, however, that this will block and may actually never return. Before calling this, you can add a timer if you want your code to actually do something in the background :) See the documentation on NSRunLoop for more ways to use this.
NSURLConnection is an asynchronous API that relies upon NSRunLoop. Your posted code never creates a run loop for the connection to run in. Therefore, I presume Cocoa is unable to create the connection and so returns nil. Things to look into:
1) Anything in the console? Is NSURLConnection throwing an exception or logging an error?
2) What happens if you use the synchronous API instead? +[NSURLConnection sendSynchronousRequest:returningResponse:error:]
3) What is the point of this code? Cocoa is not designed for running directly from a main() function yourself. Is there a particular reason why you are not using the Xcode-provided application templates that will take care of setting up run loop, autorelease pool etc.?