According to Best way to remove from NSMutableArray while iterating?, we can't remove an object from NSMutableArray while iterating, yes.
But, what if I have a code like the following
- (void)sendFeedback {
NSMutableArray *sentFeedback = [NSMutableArray array];
for (NSMutableDictionary *feedback in self.feedbackQueue){
NSURL *url = [NSURL URLWithString:#"someApiUrl"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[feedback objectForKey:#"data"] forKey:#"data"];
[request setCompletionBlock:^{
[sentFeedback addObject:feedback];
}];
[request startAsynchronous];
}
[self.feedbackQueue removeObjectsInArray:sentFeedback];
}
I'm using a NSRunLoop to create a NSThread to execute the sendFeedback method every a period of time. The way I sent data to the API is by using Asynchronous method (which will create a background thread for each request) Once the feedback has been sent, it has to be removed before NSRunner execute this method at the next period to avoid duplicate data submission.
By using asynchronous, the loop (the main thread) will continue running without waiting for the response from server. In some cases (maybe most cases), the loop will finish running before all the response from server of each request come back. If that so, the completion block's code will be execute after the removeObjectsInArray which will result in sent data remains in self.feedbackQueue
I'm pretty sure that there are several ways to avoid that problem. But the only one that I can think of is using Synchronous method instead so that the removeObjectsInArray will not be execute before all the request's response are come back (Either success or fail). But if I do so, it's mean that the internet connection has to be available for longer period. The time needed to the sendFeedback's thread will be longer. Even it will be run by newly created NSThread which will not cause the app to not respond, resources will be needed anyways.
So, is there any other way besides the one I mentioned above? Any suggestion are welcome.
Thank you.
There are a few ways to deal with this kind of problem. I suggest using a dispatch group to synchronize your feedback and using an instance variable to keep from executing a new feedback batch while one is still in progress. For this example, let's assume you create an instance variable named _feedbackUploadInProgress to your class, you could rewrite your -sendFeedback method like this:
- (void)sendFeedback
{
if( _feedbackUploadInProgress ) return;
_feedbackUploadInProgress = YES;
dispatch_group_t group = dispatch_group_create();
NSMutableArray *sentFeedback = [NSMutableArray array];
for (NSMutableDictionary *feedback in self.feedbackQueue) {
// enter the group for each item we're uploading
dispatch_group_enter(group);
NSURL *url = [NSURL URLWithString:#"someApiUrl"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[feedback objectForKey:#"data"] forKey:#"data"];
[request setCompletionBlock:^{
[sentFeedback addObject:feedback];
// signal the group each time we complete one of the feedback items
dispatch_group_leave(group);
}];
[request startAsynchronous];
}
// this next block will execute on the specified queue as soon as all the
// requests complete
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self.feedbackQueue removeObjectsInArray:sentFeedback];
_feedbackUploadInProgress = NO;
dispatch_release(group);
});
}
One approach is to keep track of the requests inflight and do the queue clean up when they are all done. Keeping track with the block is a little tricky because the naive approach will generate a retain cycle. Here's what to do:
- (void)sendFeedback {
NSMutableArray *sentFeedback = [NSMutableArray array];
// to keep track of requests
NSMutableArray *inflightRequests = [NSMutableArray array];
for (NSMutableDictionary *feedback in self.feedbackQueue){
NSURL *url = [NSURL URLWithString:#"someApiUrl"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
// save it
[inflightRequests addObject:request];
// this is the ugly part. but this way, you can safely refer
// to the request in it's block without generating a retain cycle
__unsafe_unretained ASIFormDataRequest *requestCopy = request;
[request setPostValue:[feedback objectForKey:#"data"] forKey:#"data"];
[request setCompletionBlock:^{
[sentFeedback addObject:feedback];
// this one is done, remove it
// notice, since we refer to the request array here in the block,
// it gets retained by the block, so don't worry about it getting released
[inflightRequests removeObject:requestCopy];
// are they all done? if so, cleanup
if (inflightRequests.count == 0) {
[self.feedbackQueue removeObjectsInArray:sentFeedback];
}
}];
[request startAsynchronous];
}
// no cleanup here. you're right that it will run too soon here
}
Related
I'm calling a method that will enumerate through an array, create an NSURL, and call an NSURLSessionDataTask that returns JSON. The loop typically runs about 10 times but can vary depending on the day.
I need to wait for the for loop and all NSURLSessionDataTasks to complete before I can start processing the data.
I'm having a hard time figuring out when all the work is complete. Could anyone recommend any ways or logic to know when the entire method is complete (for loop and data tasks)?
-(void)findStationsByRoute{
for (NSString *stopID in self.allRoutes) {
NSString *urlString =[NSString stringWithFormat:#"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%#", stopID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200){
NSError *jsonError = [[NSError alloc]init];
NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:#"direction"];
for (NSDictionary * _stopDictionary in stopDirectionArray) {
NSArray *stop = [_stopDictionary objectForKey:#"stop"];
[self.arrayOfStops addObject:stop];
}
}
}];
[task resume];
}
}
There are a number of options. The fundamental issue is that these individual data tasks run asynchronously, so you need some way to keep track of these asynchronous tasks and establish some dependency on their completion.
There are several possible approaches:
The typical solution is to employ a dispatch group. Enter the group before you start the request with dispatch_group_enter, leave the group with dispatch_group_leave inside the completion handler, which is called asynchronously, and then, at the end of the loop, supply a dispatch_group_notify block that will be called asynchronously when all of the "enter" calls are offset by corresponding "leave" calls:
- (void)findStationsByRoute {
dispatch_group_t group = dispatch_group_create();
for (NSString *stopID in self.allRoutes) {
NSString *urlString = [NSString stringWithFormat:#"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%#", stopID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_group_enter(group); // enter group before making request
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200){
NSError *jsonError; // Note, do not initialize this with [[NSError alloc]init];
NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:#"direction"];
for (NSDictionary *stopDictionary in stopDirectionArray) {
NSArray *stop = [stopDictionary objectForKey:#"stop"];
[self.arrayOfStops addObject:stop];
}
}
dispatch_group_leave(group); // leave group from within the completion handler
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// do something when they're all done
});
}
A more sophisticated way to handle this is to wrap the NSSessionDataTask in a NSOperation subclass and you can then use dependencies between your data task operations and your final completion operation. You'll want to ensure your individual data task operations are "concurrent" ones (i.e. do not issue isFinished notification until the asynchronous data task is done). The benefit of this approach is that you can set maxConcurrentOperationCount to constrain how many requests will be started at any given time. Generally you want to constrain it to 3-4 requests at a time.
Note, this can also address timeout issues from which the dispatch group approach can suffer from. Dispatch groups don't constrain how many requests are submitted at any given time, whereas this is easily accomplished with NSOperation.
For more information, see the discussion about "concurrent operations" in the Operation Queue section of the Concurrency Programming Guide.
For an example of wrapping NSURLSessionTask requests in asynchronous NSOperation subclass, see a simple implementation the latter half NSURLSession with NSBlockOperation and queues. This question was addressing a different topic, but I include a NSOperation subclass example at the end.
If instead of data tasks you used upload/download tasks, you could then use a [NSURLSessionConfiguration backgroundSessionConfiguration] and URLSessionDidFinishEventsForBackgroundURLSession: of your NSURLSessionDelegate would then get called when all of the tasks are done and the app is brought back into the foreground. (A little annoyingly, though, this is only called if your app was not active when the downloads finished: I wish there was a rendition of this delegate method that was called even if the app was in the foreground when the downloads finished.)
While you asked about data tasks (which cannot be used with background sessions), using background session with upload/download tasks enjoys a significant advantage of background operation. If your process really takes 10 minutes (which seems extraordinary), refactoring this for background session might offer significant advantages.
I hate to even mention this, but for the sake a completeness, I should acknowledge that you could theoretically just by maintain an mutable array or dictionary of pending data tasks, and upon the completion of every data task, remove an item from that list, and, if it concludes it is the last task, then manually initiate the completion process.
Look at dispatch groups, for your example it will look roughly like this:
create group
for (url in urls)
enter group
start_async_task
when complete leave group
wait on group to finish or supply a block to be run when completed
Ive read quite bit about blocks by now, Apple's Guide, Cocoabuilder, 3 articles on SO and ive used examples in my code that I basically got from online tutorials. Im still trying to understand one specific question. So I decided to make an app with nothing more than a completionHandler example to understand better. This is what I came up with:
ViewController
- (void)viewDidLoad
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
self.usersArray = [NSMutableArray array];
for (NSDictionary *userDict in users) {
[self.usersArray addObject:[userDict objectForKey:#"username"]];
}
//WHILE TESTING postarray method, comment this out...
//[self getPoints];
[self.tableView reloadData];
}];
}
SantiappsHelper.h/m
typedef void (^Handler)(NSArray *users);
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
NSString *urlString = [NSString stringWithFormat:#"http://www.myserver.com/myapp/getusers.php"];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod: #"GET"];
__block NSArray *usersArray = [[NSArray alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//dispatch_async(dispatch_get_main_queue(), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
// Deal with your error
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
return;
}
NSLog(#"Error %#", error);
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler){
//dispatch_sync WAITS for the block to complete before returning the value
//otherwise, the array is returned but gets zeroed out
dispatch_sync(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
Here is what I understand...
I call fetchUsersWithCompletionHandler from my VC & pass it this completion block. That block takes an NSArray users parameter & returns void.
Meanwhile in the SantiappsHelper Class we have a variable called handler of type ^block which it receives from VC.
The fetchUsersWithCompletionHandler method runs, taking that CompletionBlock parameter, which itself takes the NSArray users parameter? a little confusing.
The webfetch is dispatch_async so it wont block the main thread. So execution on the main thread continues. That new thread executes the fetch synchronously that new thread will stall until the response is returned. Once that new thread receives the response, it fills in the NSHTTPURLResponse. Once it returns, it fills in usersArray with the NSJSONSerialized data.
Finally it reaches the if test and it checks for the existence of the PARAMETER handler that was passed in? A little confusing...the parameter passed in was the completionBlock. Wouldnt that handler parameter always and obviously exist since it was passed in?
Once the handler !NULL then execution is returned to the main thread passing back the NSArray users expected by the block back in the VC.
But if I change the second dispatch to async, the usersArray is properly populated but once handler(usersArray) is sent back to the main thread, its empty or nil! Why?
Correct. The best way to say/think about this is to say that you are invoking a method called fetchUsersWithCompletionHandler:. This method will go away and do some work and at some point in the future it may execute the code you have declared in the block literal and pass in an array of users.
The method takes an argument called handler of type void (^)(NSArray *users). This type represents a block of code that when invoked should receive and array and return no result.
The fetchUsersWithCompletionHandler: does some work and at some point may invoke the block passed in with an array of users as the blocks argument.
Correct
The if (handler) { checks to see if the handler arguments is not nil. In most cases this would be the case especially if you always invoke the fetchUsersWithCompletionHandler: with a block literal, but you could always invoke the method with [self fetchUsersWithCompletionHandler:nil]; or invoked it passing along a variable from somewhere else as the completion, which may be nil. If you try to dereference nil to invoke it then you will crash.
Execution is not "passed back" to the main thread, you are simply enqueueing a block of work to be performed on the main thread. You are doing this with dispatch_sync call which will block this background thread until the block completes - this isn't really required.
The array being nil could be a consequence of you declaring the usersArray with __block storage. This is not required as you are not modifying what usersArray is pointing to at any point you are simply calling methods on it.
I know that people have asked this but I have not found satisfactory answers. I have one method that I send all my URLRequests through. I return the response of the request as a string when the method completes. I have recently added ssl to my program. This means that I can no longer use a synchronous request because I need to take advantage of the didReceiveAuthenticationChallenge function as my credentials are currently self-signing. The program needs the response from the URL in order to continue so there is not harm in waiting for the response. However, I cannot seem to find a way to just hold the code up and continue once completed. I can alert the original function that called to request function but I would like the program to pick up right after that call. And it has unique code below such calls so I cannot specialize the connectionDidFinishLoading: function because each method who calls this is different.
How can I pause the program so I can return the nsdata from the connection to the methods that called it?
Here is some pseudo-code to show you what I mean:
- (void) login:(NSString *)username :(NSString *)password {
NSString *str = [NSString stringWithFormat:%#"%#:::%#",username,password];
NSURL *url = [NSURL urlWithString:#"https://blahblahblah"];
NSString *result = [self connectToUrl:str:url];
if ([result isEqualToString:#"valid"]) {
//this would be more complex in here
NSLog(#"hooray");
} else {
NSLog(#"bummer");
}
}
- (NSString *)connectToUrl:(NSURL *)url :(NSString *)str {
NSData *FileData = [str dataUsingEncoding: NSUTF8StringEncoding];
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:100];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
//set up the rest of the request...
...
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
//WOULD LIKE TO PAUSE HERE UNTIL COMPLETE! THEN CONTINUE
// received data is assigned in didReceiveData: method
return [[[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding] autorelease];
}
But alas, I cannot do this because I cannot make the final line wait until the connection is complete... Please help me!
Very appreciative!
R
iOS and OS X and much of the Cocoa/Cocoa touch frameworks are built on an event model. You don't pause your app. That's not the proper approach. You need to start the connection and then move on. When the connection completes, you act on that event.
In other words, your login method can't sit and wait for the result. It should start the connection and return.
When you get the result of the connection you call some method to process the login result.
Making use of blocks can make things like this easier but there are other ways. You just need to stop thinking about such things in a linear fashion. Dealing with asynchronous processing requires a different approach.
By going to a url test.com/test.cfm I am able to output the following:
{"COLUMNS":["OBJID","USERNAME","USERPASSWORD","USERFNAME","USERMI","USERLNAME","COMPANY","TBLCOMPANYID","TBLTITLE","USERADDRESS1","USERADDRESS2","USERCITY","USERSTATE","USERZIP","USERSIGNATUREFILE","USERBUSINESSNUMBER","USERBUSINESSEXT","USERFAXNUMBER","USERCELLNUMBER","USEROTHERNUMBER","USEREMAIL1","USEREMAIL2","USEREMAIL3","DEFAULTPROJECTID","SIGNATURE","SIGNATUREUPLOADBY","SORTORDER","DISABLESTATUS","UUID","SITEID","PROGRAMID"],
"DATA":[[1,"test",11214.0,"admin","","admin","adf Inc.",1,1,"admin","","","California","","","",null,"","","","admin#test.com","","",0,null,null,0,false,"468373c5-1234-1234-1234-3133a2bb1679",62,1]]}
To iterate through this I will first need to get the data using this?
NSMutableData *receivedData;
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"test.com/test.cfm"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:receivedData];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
NSLog(#"test = %#",receivedData);
} else {
// Inform the user that the connection failed.
}
Am I on the right track? The output says null...
You've not assigned anything to receivedData; it'll either be nil (under ARC) or an undefined value which may or may not be nil (under non-ARC). You've created an object that could be used to initiate a URL connection but done nothing with it. You're also probably not getting a valid NSURL because you've failed to specify the URI scheme (ie, the http://).
Probably the easiest thing to do (assuming at least iOS 5 and/or OS X v10.7) would be to use NSURLConnection's +sendAsynchronousRequest:queue:completionHandler: and then NSJSONSerialization to parse the result. E.g.
NSURLRequest *theRequest =
[NSURLRequest
requestWithURL:[NSURL URLWithString:#"http://test.com/test.cfm"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// or just use [NSURLRequest requestWithURL:[NSURL ...]], since the
// protocol cache policy and a 60s timeout are the default values anyway
[NSURLConnection
sendAsynchronousRequest:theRequest
queue:[NSOperationQueue mainQueue]
completionHandler:
^(NSHTTPURLResponse *urlResponse, NSData *data, NSError *error)
{
// catch all for connection errors...
if(
(urlResponse.statusCode < 200) ||
(urlResponse.statusCode >= 300) || // bad status code, e.g. 404
error || // error is non-nil would imply some other error
![data length] // data returned was empty
)
{
// report some sort of connection error
return;
}
NSError *jsonError = nil;
id <NSObject> returnedJSONObject =
[NSJSONSerialization
JSONObjectWithData:data options:0 error:&jsonError];
if(jsonError)
{
// server returned unintelligible JSON...
return;
}
NSLog(#"Got object %#", returnedJSONObject);
// then, e.g.
if(![jsonObject isKindOfClass:[NSDictionary class]])
{
// server didn't return a dictionary
return;
}
NSArray *columns = [jsonObject objectForKey:#"COLUMNS"];
if(![columns isKindOfClass:[NSArray class]])
{
// server returned no COLUMNS object, or that
// object wasn't an array
return;
}
NSLog(#"columns are %#", columns);
/* etc, etc */
}];
The class type checking stuff quickly gets quite tedious if you don't find a way to automate it but that's all validation stuff not directly related to your question.
What the above achieves is that it sends an asynchronous (ie, non-blocking) request for the contents of the URL. Results are accumulated on the main queue (ie, the same place you'd normally do user interactions). Once that entire HTTP operation is complete the code you specified at the bottom is called and that validates and parses the response. It does so synchronously so will block but that's not worth worrying about unless profiling shows it's worth worrying about.
The built-in parser is used for the JSON and everything down to 'Got object' is really just ensuring that the fetch and parse succeeded. It may not be complete — if you can think of anything else that might go wrong then don't assume I've ignored it on purpose.
At that point you just have an object of unknown type but it'll normally be a dictionary or an array because of the fundamentals of JSON. So the example code tests that it really is a dictionary then uses the normal NSDictionary interface to obtain an object for the 'COLUMNS' key. If you attempted to call objectForKey: on an array you'd raise an exception since arrays don't implement that method.
It's then fairly rote — the code checks that the object stored as 'COLUMNS' was an array. Per the JSON rules it could have been another dictionary or a string or one of a few other things. Possibly of interest is that the code calls isKindOfClass: to test that an object was found and that it was an array in a single call; that works because it's explicitly permissible to send any message to nil and the result will always be nil, which looks the same as a BOOL NO.
I am writing a network class for an iOS app. This class will take care of all logging and network traffic. I have a problem where I have to send possibly thousands of requests at one time, but NSURLConnections are timing out because the delegate methods will not be called until all the NSURLConnections are started, by which time the timeout period has expired. I am using a rest API for Drupal and, unfortunately, I do not know of a way to create multiple instances with one request. How can I receive responses while simultaneously sending them? If I use GCD to pass off the creation of the NSURLConnections, will that solve the problem? I think I would have to pass the entire operation of iterating over the objects to send and sending to GCD to free up the main thread to answer to responses.
-(BOOL)sendOperation:(NetworkOperation)op
NetworkDataType:(NetworkDataType)dataType
JsonToSend:(NSArray *)json
BackupData:(NSArray *)data
{
if(loggingMode)
{
return YES;
}
NSURLConnection *networkConnection;
NSData *send;
NSString *uuid = [self generateUUID];
NSMutableArray *connections = [[NSMutableArray alloc] init];
NSMutableURLRequest *networkRequest;
for (int i=0; i<[json count] && (data ? i<[data count] : YES); i++)
{
if(op == Login)
{
/*Grab all cookies from the server domain and delete them, this prevents login failure
because user was already logged in. Probably find a better solution like recovering
from the error*/
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:
[[NSURL alloc] initWithString:networkServerAddress]];
for (NSHTTPCookie *cookie in cookies)
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
}
networkRequest = [[NSMutableURLRequest alloc] initWithURL:
[NSURL URLWithString:[networkServerAddress stringByAppendingString:#"/user/login"]]];
}
else if(op == StartExperiment)
{
networkRequest = [[NSMutableURLRequest alloc] initWithURL:
[NSURL URLWithString:[networkServerAddress stringByAppendingString:#"/node"]]];
}
else if(op == Event || op == EndExperiment || op == SendAll)
{
networkRequest = [[NSMutableURLRequest alloc] initWithURL:
[NSURL URLWithString:[networkServerAddress stringByAppendingString:#"/node"]]];
}
else if(op == Logout)
{
networkRequest = [[NSMutableURLRequest alloc] initWithURL:
[NSURL URLWithString:[networkServerAddress stringByAppendingString:#"/user/logout"]]];
}
send = [[json objectAtIndex:i] dataUsingEncoding:NSUTF8StringEncoding];
//Set the headers appropriately
[networkRequest setHTTPMethod:#"POST"];
[networkRequest setValue:#"application/json"
forHTTPHeaderField: #"Content-type"];
[networkRequest setValue:[NSString stringWithFormat:#"%d", [send length]]
forHTTPHeaderField:#"Content-length"];
[networkRequest setValue:#"application/json"
forHTTPHeaderField:#"Accept"];
//Set the body to the json encoded string
[networkRequest setHTTPBody:send];
//Starts async request
networkConnection = [[NSURLConnection alloc] initWithRequest:networkRequest delegate:self];
//Successfully created, we are off
if(networkConnection)
{
[networkConnectionsAndData setValue:[[NSMutableArray alloc] initWithObjects:uuid,
[[NSNumber alloc] initWithInt:op], [[NSNumber alloc] initWithInt:dataType], [[NSMutableData alloc] init], (data ? [data objectAtIndex:i] : [NSNull null]), nil]
forKey:[networkConnection description]];
}
else //Failed to conn ect
{
NSLog(#"Failed to create NSURLConnection");
return NO;
}
}
[[self networkOperationAndConnections] setObject:[[NSMutableDictionary alloc] initWithObjectsAndKeys:[[NSMutableArray alloc] initWithObjects:connections, nil], #"connections", [[NSMutableArray alloc] init], #"errors", nil]
forKey:uuid];
return YES;
}
The dictionaries are used to keep track of the correlating data with each NSURLConnection and also to group the NSURLConnections together into one group to determine ultimate success or failure of an entire operation.
Update
AFNetworking was key in finishing this project. It not only cleaned up the code substantially, but dealt with all the threading issues inherit in sending so many requests. Not to mention with AFNetworking I could batch all the requests together into a single operation. Using blocks, like AFNetworking uses, was a much cleaner and better solution than the standard delegates for NSURLConnections.
You definitely need to allow the NSURLRequest / Connection to be operating on another thread. (Not the main thread!)
Edited for clarity**:
I noticed your comment of "//Starts async request" and I wanted to be sure you realized that your call there is not what you would expect out of a typical "asynch" function. Really its just firing off the request synchronously, but since its a web request it inherently behaves asynchronously. You want to actually place these requests on a another thread for full asynch behavior.
Everything else aside, I really suggest digging into Apple's networking example project here: MVCNetworking
As for specifics on your question, there's a couple ways to do this.
One is to keep your connection from starting immediately using initWithRequest:<blah> delegate:<blah> startImmediately:FALSE and then schedule your NSURLConnection instances on another thread's run-loop using: scheduleInRunLoop:forMode:
(Note: You then have to kick off the connection by calling start-- it's best to do this via an NSOperation + NSOperationQueue.)
Or use this static method on NSURLConnection to create/launch the connection instead of doing an alloc/init: sendAsynchronousRequest:queue:completionHandler:
(Note: this approach accomplishes pretty much same as above but obfuscates the details and takes some of the control out of your hands.)
To be honest my quick answers above won't be sufficient to finish this kind of project, and you'll need to do a bit of research to fill in the blanks, especially for the NSOperationQueue, and that's where the MVCNetworking project will help you.
Network connections are a fickle beast -- You can time-out and kill your connections even if they're running on a background thread simply by trying to perform too much work simultaneously! I would seriously reconsider opening up several thousand NSURLConnections at once, and using an NSOperationQueue would help work around this.
ADDITIONAL RESOURCES:
Here's a 3rd party library that may make your networking adventures less painful:
https://github.com/AFNetworking/AFNetworking
http://engineering.gowalla.com/2011/10/24/afnetworking/