concurrent way to load multiple loadRequest of webView - iphone

Currently I have about 4 to 5 different webView's which I am trying to load simultaneously, however I have come across the conclusion that only one loadRequest can load at a time. Although I have created custom class for each webview, the loadRequest, doesn't seams to get called, while if there is another loadRequest of a webview is being utilized.
Is there anyway to keep these calls in different Thread's to make it work or utilize in dispatch mechanism? Just trying to figure out if there is an alternative.
Thanks

use NSUrlConnection to preload the data, no threading
//for EACH url to load
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:tweet[#"profile_image_url"]]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue] // one should ideally use a different queue here to free main thread and ONLY do the imageView.image setting in Main thread
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if(!error) {
//load webview! with Data maybe!
}
}];

Since you seem to have only 4 or 5 web views, it is not that inefficient to call them on a dispatch queue. If there are 100's of requests, then you need to think of some strategies though.
NSArray *myWebSites = [NSArray arrayWithObjects:#"www.cnn.com",#"www.fox.com",....,nil];
for(url in myWebsites){
dispatch_async(dispatch_get_global_queue, ^{
// get the data from the url.
// do the display in main queue
dispatch_async(dispatch_get_mainqueue, ^{
//load the web view;
}
}
}

Related

How to perform an asynchronous request on a background thread?

I have a method foo: that is called on a background thread. This method simply sends a request to a server, and, after data are retrieved, performs some calculations about those data and returns. In this case I prefer to use sendSynchronousRequest: because this method is convenient and it doesn't matter if the thread is blocked. However, the response contains a "Location" header field that will redirect to another page. I want to read the response to get those "Set-Cookie" header fields before redirection. It seems that the synchronous method does not allow me to.
I tried to use the asynchronous one and implement a NSURLConnectionDataDelegate, but the thread is finished before those methods of the delegate is called. (I suppose the way that Apple implements the asynchronous one is to perform those time-consuming works on a new thread)
Is there any way to solve this problem? Since performing an asynchronous request on the main thread may add complexity to my program.
The foo: method is kind of like this
- (Result *)foo
{
NSURLMutableRequest * request = blablabla;
//Do something to initialize the request
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
//Do something with the data
Result *result = [[Result alloc] init] autorelease];
//fill the result
return result;
}
You could use a Grand Central Dispatch semaphore to wait until the asynchronous request returns:
- (Result *)foo
{
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
// set request's properties here
__block Result *result;
dispatch_semaphore_t holdOn = dispatch_semaphore_create(0);
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error)
{
// handle error
}
else
{
result = [[Result alloc] initWithData:data];
}
dispatch_semaphore_signal(holdOn);
}];
dispatch_semaphore_wait(holdOn, DISPATCH_TIME_FOREVER);
return result;
}
NOTE: This code requires iOS 4.0+ and ARC!
Look into [NSCondition] which enables you to wait and signal threads
Basically you allocate a NSCondition and in the block you'll have [condition wait]; which will cause the thread to wait. then, when the async operation is done, you call [condition signal]; which will signal the waiting thread to continue.
http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/NSCondition_class/Reference/Reference.html
You can create your own NSRunLoop and do your requests there. Stop the run loop once you're done with your requests.
Or if you are lazy like me and don't want to mess with run loops, just put your connection on the main thread:
dispatch_async(dispatch_get_main_queue(), ^(void){
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
}
You can find a small and simple class that lets you do this on github. It provides two primary objects - a NSOperationsQueue manager and NSOperation subclasses designed to run background fetches. As was mentioned, if all you were doing was fetches, you could do that on the main thread. But if you want to do data processing too, then this project will let you do that in a completed method.
A nice property of the OperationsRunner class is that you can cancel operations at any time (when for instance the user taps the back button), and everything gets torn down quickly with no stalling or leaking.
However, if all you ever do is this one fetch and one process, then you could as others have said just fetch the data on the main thread, and once you have it then dispatch a "processing" block to one of the concurrent system threads, and when that processing is done, dispatch another message to the main thread telling you that the work is complete.

check which request is which from NSURLConnection delegate

What is the best way to check which request is which inside the delegate method:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
}
Right now I have a NSURLConnection that I set to the NSURLConnection before making a request and inside didReceiveResponse I do:
if (self.tempConnection == connection)
however there is a possiblity this won't work for race conditions. Is there a better way to do this?
There is a better way in OS5. Forget about all those bothersome delegate messages. Let the connection build the data for you, and put your finished code right in line with your start code:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.site.com"]];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"got response %d, data = %#, error = %#", [httpResponse statusCode], data, error);
}];
I've looked at a bunch of different ways to do this, and I've found that by far the cleanest and easiest in order to manage is to use a block pattern. That way you are guaranteed to be responding to the right request upon completion, avoid race conditions, and you don't have any issues with variables or objects going out of scope during the asynchronous call. It's also a lot easier to read/maintain your code.
Both ASIHTTPRequest and AFNetworking APIs provide a block pattern (however ASI is no longer supported so best to go with AFNetworking for new stuff). If you don't want to use one of these libraries, but want to do it yourself, you can download the source for AFNetworking and review their implementation. However, that seems like a lot of extra work for little value.
Consider creating a separate class to serve as the delegate. Then, for each NSURLConnection spawned, instantiate a new instance of the delegate class to for that NSURLConnection
Here's some brief code to illustrate this:
#interface ConnectionDelegate : NSObject <NSURLConnectionDelegate>
...then implement the methods in the .m file
Now, I'm guessing you probably have the code you posted in a UIViewController subclass (or some other class serving different purposes)?
Wherever you are kicking off the requests, use this code:
ConnectionDelegate *newDelegate = [[ConnectionDelegate alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
//then you can repeat this for every new request you need to make
//and a different delegate will handle this
newDelegate = [[ConnectionDelegate alloc] init];
request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
// ...continue as many times as you'd like
newDelegate = [[ConnectionDelegate alloc] init];
request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
You might consider storing all the delegate objects in a NSDictionary or some other data structure to keep track of them. I'd consider using an NSNotification in connectionDidFinishLoading to post a notification that the connection is done, and to serve whatever object created from the response. Lemme know if you want code to help you visualize that. Hope this helps!

iOS 5 NSURLConnection - Making Multiple Connections with UI Feedback

I am currently designing an iOS 5 iPhone app that will use a .NET RESTful Web Service to provide data updates. When the application is initially installed, it will connect to the WS to download all the data in a JSON format. Thereafter, it will only perform updates. The WS provides a POST method for each table as GetAllTableRecords() and GetLastUpdatedTableRecords().
I am using iOS 5 and I've got the NSURLConnection and JSON serialization/deserialization working correctly with native libraries. Each WS POST method call is currently residing in its own Obj-C class with all of the delegate methods. Additionally, each class handles the local datastore inserts and updates.
Each NSURLConnection is asynchronous and all of the WS calls are driven off of button events from view controllers.
My questions are:
Is this the right setup in terms of code encapsulation and reuse?
How do I handle making multiple WS calls while keeping the user
informed via the UI?
Currently there are two tables to download. This means the app will call the WS twice to get the initial data and twice again during each refresh. I know that since each NSURLConnection is asynchronous, the connection will make the request but the UI will continue on while the delegate handles the data download. I've done some research into GCD and NSOperation/Queue but I don't know enough about either one to code a solution or know if that's even a correct solution.
Any insight would be most helpful!
Edit #1: What about providing real time updates back to the UI? The Mint app does something similar when updating transactions and accounts. They have a little status bar that pops up at the bottom while requests are made.
Edit #2: Ok, I believe I've made some progress. We are using Story Boards and the entry point is the Login View/Controller. When the login button is clicked, a NSURLConnection is made to the webservice. If the response status code is 200 in connectionDidFinishLoading:(NSURLConnection *)connection, a segue is performed to go the Data Sync View. The purpose of this view is to either initialize or update the database while providing feedback to the user. Either updating or initializing requires two additional web service calls.
Here's my DataSyncView.m:
#synthesize pvStatus, lbStatus;
// pvStatus = progress indicator
// lbStatus = label
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self StartDataSync];
}
- (void)StartDataSync
{
[lbStatus setText:#"Syncing data..."];
[pvStatus setProgress:0.0f];
// TODO: Determine if database is popuplated
[self PerformInitialSync];
// Next screen
[self performSegueWithIdentifier:#"SegueFromSync" sender:self];
}
// Populates data store will data from web service
- (void)PerformInitialSync
{
// Kicks off a series of synchronous requests
[self DownloadAllEmployeeDataA];
}
- (void)DownloadAllDataA
{
// Dictonary holds POST values
NSMutableDictionary *reqDic = [NSMutableDictionary dictionary];
// Populate POST key/value pairs
[reqDic setObject:passWord forKey:#"Password"];
[reqDic setObject:userName forKey:#"UserName"];
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:reqDic options:NSJSONWritingPrettyPrinted error:&error];
// Convert dictionary to JSON
NSString *requestJSON = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
// Declare Webservice URL, request, and return data
NSURL *url = [[NSURL alloc] initWithString:#"http://wsurl/getalla"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSData *postData = [NSData dataWithBytes:[requestJSON UTF8String] length:[requestJSON length]];
// Build the request
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", [postData length]] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
[request setCachePolicy:NSURLRequestUseProtocolCachePolicy];
[request setTimeoutInterval:60.0];
NSURLResponse *response;
[lbStatus setText:#"Downloading employee data..."];
[pvStatus setProgress:0.1f];
// Make the response
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// If return data received
if(returnData)
{
// Get the response and check the code
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int code = [httpResponse statusCode];
// Check to make sure successful code
if (code == 200)
{
// Convert JSON objects to Core Data Entity
// Update UIProgressView and Label
// Call next WS call
[self DownloadAllEmployeeDataA];
}
}
}
- (void)DownloadAllDataB
{
// Same code as above but with different URL and entity
}
My problem that I am having is this: The UIProgressView and Label are not updating as the calls are being made. As I stated before, I don't even know if this is the best way to make these calls. It doesn't appear that I'm blocking the main thread but I could be wrong. Again, I'll pose the question: what is the best way to make multiple url calls while keeping the UI updated on the progress? Thanks!!!
// Make the response
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
In your question you said you made asynchronous load of url request. But in the above line of code you are making a synchronous request ?
Is this the right setup in terms of code encapsulation and reuse?
Looking at your code you are not adhering to MVC. Your View
Controller shouldn't manage loading URL connections. You can create a
class that does that and using delegates inform the view controller
whether data is downloaded or failed to download,etc.
How do I handle making multiple WS calls while keeping the user informed via the UI?
If you want to make concurrent URL Connections then use NSOperation
and NSOperationQueue. Try avoiding GCD ( Refer to WWDC 2010 Session
208 ).
My problem that I am having is this: The UIProgressView and Label are not updating as the calls are being made.
You are making a Synchronous URL request on main thread. As per your
code UIProgressView shouldn't update.
Refer URL Loading System Programming Guide
Another comment I have is your method names, start method name with small letter. Rest of it looks fine. Coding Guidelines for Cocoa

AFNetworking + download images in queue + cancel operations

I need to download a queue of images.
I created my operations first, then add them with the "enqueue" method of AFNetworking.
I have 2 problems :
1) I didn't have the progress bar working for the queue (and I have it working with a custom operation queue)
2) I didn't find the solution to stop the queue when I want
I created first operations to batch and add theme in a array:
while ((dict = [enumerator nextObject]))
{
NSMutableURLRequest *request = [[MyHTTPClient sharedClient] requestWithMethod:#"GET" path:#"ws/webapp/services/pull_image" parameters:dict];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil cacheName:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
NSLog(#"image : %#", [image description]);
// process images
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
// manage errors
}];
[operations addObject:operation];
}
Then, I enqueue the operations:
[[MyHTTPClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations
progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations)
{
float percentDone = ((float)((int)numberOfCompletedOperations) / (float)((int)totalNumberOfOperations));
[delegate syncServicesController:self updateProgressView:percentDone];
}
completionBlock:^(NSArray *operations)
{
//
}];
So, the progress download didn't work.
But I can see the progress of numberOfCompletedOperations... ? 1,2,3,4,5... Does I need to force the refresh of the progress view in the main thread ?
And when I tried to stop the network tasks:
- (void)cancelAllRequests
{
[[MyHTTPClient sharedClient] cancelAllHTTPOperationsWithMethod:#"GET" path:#"ws/webapp/services/pull_image"];
}
I don't understand how to stop the queue of requests... This seems that works but I have this error : -[NSBlockOperation request]: unrecognized selector sent to instance 0x16f54c70
These were actually just fixed in the last day or two :)
Go ahead and update to the latest version of master, which includes the following:
cc2115e469: Progress blocks now dispatch to main by default, just like all of the other completion blocks in AFNetworking. This should fix any issues around the UI not updating there.
cac44aeb34: Fixes that problem with NSBlockOperation being sent request. There was an incorrect assumption baked into cancelAllHTTPOperationsWithMethod: that all operations were AFHTTPRequestOperation. The only downside is that it will not handle your batched operations. For that, you can always iterate through httpClient.operationQueue.operations and pick out the one you want.

NSURLConnection - how to wait for completion

Our iPhone app code currently uses NSURLConnection sendSynchronousRequest and that works fine except we need more visibility into the connection progress and caching so we're moving to an async NSURLConnection.
What's the simplest way to wait for the async code to complete? Wrap it in a NSOperation/NSOperationQueue, performSelector..., or what?
Thanks.
I'm answering this in case anyone else bumps into the issue in the future. Apple's URLCache sample code is a fine example of how this is done. You can find it at:
iOS Developer Library - URLCache
As John points out in the comment above - don't block/wait - notify.
To use NSURLConnection asynchronously you supply a delegate when you init it in initWithRequest:delegate:. The delegate should implement the NSURLConnection delegate methods. NSURLConnection processing takes place on another thread but the delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object.
Apart from notifications mentioned prior, a common approach is to have the class that needs to know about the URL load finishing set itself as a delegate of the class that's handling the URL callbacks. Then when the URL load is finished the delegate is called and told the load has completed.
Indeed, if you blocked the thread the connection would never go anywhere since it works on the same thread (yes, even if you are using the asynch methods).
I ran into this because our app used NSURLConnection sendSynchronousRequest in quite a few places where it made sense, like having some processing occurring on a background thread occasionally needing extra data to complete the processing. Something like this:
// do some processing
NSData * data = someCachedData;
if (data = nil) {
data = [NSURLConnection sendSynchronousRequest....]
someCachedData = data;
}
// Use data for further processing
If you have something like 3 different places in the same flow that do that, breaking it up into separate functions might not be desirable(or simply not doable if you have a large enough code base).
At some point, we needed to have a delegate for our connections(to do SSL certificate pinning) and I went trolling the internet for solutions and everything was of the form: "just use async and don't fight the framework!". Well, sendSynchronousRequest exists for a reason, this is how to reproduce it with an underlying async connection:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error
{
static NSOperationQueue * requestsQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
requestsQueue = [[NSOperationQueue alloc] init];
requestsQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
});
NSCondition * waitLock = [NSCondition new];
[waitLock lock];
__block NSError * returnedError;
__block NSURLResponse * returnedResponse;
__block NSData * returnedData;
__block BOOL done = NO;
[NSURLConnection sendAsynchronousRequest:request
queue:requestsQueue
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError){
returnedError = connectionError;
returnedResponse = response;
returnedData = data;
[waitLock lock];
done = YES;
[waitLock signal];
[waitLock unlock];
}];
if (!done) {
[waitLock wait];
}
[waitLock unlock];
*response = returnedResponse;
*error = returnedError;
return returnedData;
}
Posted here in case anyone comes looking as I did.
Note that NSURLConnection sendAsynchrounousRequest can be replaced by whatever way you use to send an async request, like creating an NSURLConnection object with a delegate or something.