Pause and Resume ASINetworkQueue - ios

I'm using an ASINetworkQueue to download around 50 files in an iPad app. I'm looking for a way of allowing the user to pause and resume the queue.
The ASIHTTP docs refer to
[request setAllowResumeForFileDownloads:YES];
but this operates at an individual request level, not at the queue level. As ASINetworkQueue is a subclass of NSOperationQueue I've also tried
[queue setSuspended:YES];
and while this will pause a queue, it does not affect the downloads in progress, it just waits until they've finished and then pauses the queue, which in my case means many seconds between the user pressing the button and the queue actually pausing, which is not the UI experience I want.
Can anyone suggest another way of solving this problem?

My solution..
- (void) pause
{
if(self.queue && self.queue.requestsCount>0)
{
NSLog(#"%#", self.queue.operations);
for (ASIHTTPRequest * req in self.queue.operations) {
req.userInfo = [NSDictionary dictionaryWithObject:#"1" forKey:#"ignore"];
}
[self.queue.operations makeObjectsPerformSelector:#selector(cancel)];
[self.queue setSuspended:YES];
for (ASIHTTPRequest * req in self.queue.operations) {
ASIHTTPRequest * newReq = [[ASIHTTPRequest alloc] initWithURL:req.url];
[newReq setDownloadDestinationPath:req.downloadDestinationPath];
[newReq setTemporaryFileDownloadPath:req.temporaryFileDownloadPath];
// [newReq setAllowResumeForFileDownloads:YES];
[newReq setUserInfo:req.userInfo];
[self.queue addOperation:newReq];
}
}
}
- (void) resume
{
if(self.queue && self.queue.requestsCount>0)
{
[self _setupQueue];
[self.queue go];
}
}
- (void) _setupQueue
{
[self.queue setShouldCancelAllRequestsOnFailure:NO];
[self.queue setRequestDidStartSelector:#selector(downloadDidStart:)];
[self.queue setRequestDidFinishSelector:#selector(downloadDidComplete:)];
[self.queue setRequestDidFailSelector:#selector(downloadDidFailed:)];
[self.queue setQueueDidFinishSelector:#selector(queueDidFinished:)];
[self.queue setDownloadProgressDelegate:self.downloadProgress];
[self.queue setDelegate:self];
self.queue.maxConcurrentOperationCount = 3;
// self.queue.showAccurateProgress = YES;
}
First, pause function cancel all running operations, and recreate new requests push them into queue.
Then resume function unsuspends the queue.
Be noticed, request should not set setAllowResumeForFileDownloads:YES, otherwise the totalBytesToDownload will calculate wrong..If allowResumeForFileDownloads=NO, its value will be same as the count of requests in queue.
Here is my request fail handler, I add retry when file download fail. But I don't whant when I cancel a request, the retry mechanism will be invoked, so I set userInfo(ignore:true) to request object to prevent it happens.
- (void) downloadDidFailed:(ASIHTTPRequest *)req
{
NSLog(#"request failed");
NSLog(#"%d", self.queue.requestsCount);
if(![self.queue showAccurateProgress])
{
[self.queue setTotalBytesToDownload:[self.queue totalBytesToDownload]-1];
NSLog(#"totalBytesToDownload=%lld", self.queue.totalBytesToDownload);
}
NSDictionary * userInfo = req.userInfo;
if([[userInfo valueForKey:#"ignore"] boolValue]) return; // ignore retry
int retryTimes = [[req.userInfo objectForKey:#"retryTimes"] intValue];
if(retryTimes<kMU_MaxRetry)
{
++ retryTimes;
NSLog(#"retry %d", retryTimes);
ASIHTTPRequest * newReq = [ASIHTTPRequest requestWithURL:req.url];
[newReq setDownloadDestinationPath:req.downloadDestinationPath];
[newReq setTemporaryFileDownloadPath:req.temporaryFileDownloadPath];
newReq.userInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%d", retryTimes] forKey:#"retryTimes"];
[self.queue addOperation:newReq];
NSLog(#"%d", self.queue.requestsCount);
}else{ // reach max retry, fail it
[self.failures addObject:req];
}
}
I am not sure if there is a better solution, hope it can help you.

Related

BGTaskScheduler: How do you continue an operation in queue in the background?

I have set up all the steps required for adding the capability for background process (identified the task, registered and calling as instructed in this video).
I wondered if I'm downloading a file and before the download ends, the app goes to the background – what can I do to keep the download running? I just don't know about the last part in this method where I need to call on my last item in a queue and keep running the download.
The code shown is in Objective-C, but any hints in Swift would be extremely helpful as well.
- (void) handelBackgroudProcessingTaskForDownloads:(BGProcessingTask *) task {
[self scheduleAppBGProcessingTaskForDownloads];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
task.expirationHandler = ^{
[queue cancelAllOperations];
};
__weak NSOperation* lastOperation = MyDownloadService.shared.queue.operations.lastObject;
if (lastOperation != nil) {
lastOperation.completionBlock = ^{
[task setTaskCompletedWithSuccess: !lastOperation.isCancelled];
};
} else {
[task setTaskCompletedWithSuccess: !lastOperation.isCancelled];
}
//this is for the case that I have more than one file in the queue which they have not started downloading yet
if ((! lastOperation.isExecuting) && (! lastOperation.isCancelled) ) {
[queue addOperation:MyDownloadService.shared.queue.operations.lastObject];
}
// My question: How can I keep downloading the single file when app goes in bg before download is done? I tried this line below and did not work!
if(lastOperation.isExecuting) {
[MyDownloadService.shared.queue waitUntilAllOperationsAreFinished];
}

How to do async task in non-concurrent custom nsoperation

Maybe I let NSOperation to play a wrong role in non-concurrent job. My requirement is , I want to do a lot of async jobs, but I want them to be completed in order. When task1 is finished after the async callback, task2 can be take into work now. And I make all the task a NSOperation. However, NSOperation is used to multiple thread programming most time. Is my choice wrong. But it remind me to think more about the NSOperation in this case, we can't manage manually the isFinished and isExecute in a sync block since the operation have been release in non-concurrent nsoperation,it means i couldnot use the powerful operation queue to automatically manage the task.Any idea?Thanks for your answer..
edit with code :
-(void)main {
[super main];
self.isOperationExcuting = YES;
self.isOperationFinished = NO;
WEAKSELF
[self query:^(NSArray *array, NSError *error) {
//I set my custom property, but it do not cause my NSOperation to be finished
weakSelf.isOperationFinished = YES;
weakSelf.isOperationExcuting = NO;
}];
}
-(void)query:(void (^)(NSArray *array, NSError *error))block {
BmobQuery *query = [BmobQuery queryWithClassName:#"Room"];
[query findObjectsInBackgroundWithBlock:block];
}
-(BOOL)isFinished {
return self.isOperationFinished;
}
- (BOOL)isExecuting {
return self.isOperationExcuting;
}
- (void)start {
[super start];
NSLog(#"start");
}
- (void)cancel {
[super cancel];
NSLog(#"cancel");
}
Just make all added operations serial by setting maxConcurrentOperationCount to 1.
NSOperationQueue* queue = [[ NSOperationQueue alloc ] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
NSOperationQueue
The NSOperationQueue class regulates the execution of a set of
NSOperation objects. After being added to a queue, an operation
remains in that queue until it is explicitly canceled or finishes
executing its task.

NSOperationQueue does not cancel (remove) operations

I'm looking for a means to handle separate but related NSURLRequest and thought that I could add them to an NSOperationQueue and then manage them (run the request or not based on http status code - if the status code is 200 they can run, if not, stop all of them as the url string needs to be appended).
In my test code below I suspend the OQ to stop the processing of NSURLRequest (represented here by some public RSS feeds) but continue to the request to the OQ. I get the right number of operations (4). After adding all request to the OQ I then check to see if it has been suspended and if so, cancel all the operations.That works, at least the check if it has been suspended.
When I do a count check after canceling the operations I still get 4 but was expecting less (and hoping for 0). I'm using NSURLConnection to get the rss data in a NSObject subclass.
I understand from the docs that NSOQ will not remove an operation until it has reported that it is finished. (Is there a way to see this report?)
You cannot directly remove an operation from a queue after it has been added. An operation remains in its queue until it reports that it is finished with its task. Finishing its task does not necessarily mean that the operation performed that task to completion. An operation can also be canceled. Canceling an operation object leaves the object in the queue but notifies the object that it should abort its task as quickly as possible.
NSURLConnection doesn't have a willStart or similar delegate method so I can't track that but my feeling is the second RSS feed is in some sort of start process and that would explain why it is still in there. But I log the connectionDidFinishLoading delegate and so the first task is completed, so I was expecting at least that to be gone.
So my question is twofold.
1. If I nil out NSOQ, does that eliminate the operations within it? And what danger is there if one of those operations is in process - crash, hanging the app, etc?
2. Is there a way to cancel a NSURLConnection that is in process? (Assuming that the answer to 1 is yes, you are in the danger zone).
Here's my code:
- (void)viewDidLoad {
[super viewDidLoad];
connectionManager* myConnectionManager = [[connectionManager alloc] init];
NSOperationQueue* operationQueue = [[NSOperationQueue alloc] init];
NSMutableArray* arrAddedOperations = [[NSMutableArray alloc] init];
NSArray* arrFeeds = [[NSArray alloc] initWithObjects:#"http://rss.cnn.com/rss/cnn_topstories.rss", #"http://hosted.ap.org/lineups/USHEADS-rss_2.0.xml?SITE=RANDOM&SECTION=HOME", #"http://feeds.reuters.com/reuters/topNews", #"http://newsrss.bbc.co.uk/rss/newsonline_world_edition/americas/rss.xml", nil];
//add operations to operation queue
for(int i=0; i<arrFeeds.count; i++) {
NSInvocationOperation* rssOperation = [[NSInvocationOperation alloc]
initWithTarget: myConnectionManager
selector:#selector(runConnection:)
object:[arrFeeds objectAtIndex:i]];
//check to put a suspension on the OQ
if (i>1) {
operationQueue.suspended = YES;
}
[operationQueue addOperation:rssOperation];
[arrAddedOperations addObject:[arrFeeds objectAtIndex:i]];
//incremental count to see operations being added to the queue - should be 4
NSLog(#"This is the number of operations added to the queue:%i", [operationQueue operationCount]);
}
if (operationQueue.suspended) {
//restart the OQ so we can cancel all the operations
operationQueue.suspended = NO;
//kill all the operations
[operationQueue cancelAllOperations];
//count to see how many operations are left
NSLog(#"OQ has been suspended and operations canclled. The operation count should be 0\nThe operation count is %i", [operationQueue operationCount]);
}
}
from NSURLConnection class
- (void) runConnection : (NSString*) strURL {
NSURLRequest* urlRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:strURL]];
self.myConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO];
[self.myConnection setDelegateQueue:self.myQueue];
[self.myConnection start];
self.myConnection = nil;
}
#pragma mark - NSURLConnection Delegates
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"%#", error.localizedDescription);
}
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"%#", [NSNumber numberWithInteger:httpResponse.statusCode]);
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
self.strReponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//NSLog(#"%#", self.strReponse);
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"task finished");
NSDictionary* dictUserInfo = [[NSDictionary alloc] initWithObjectsAndKeys:
#"Display Data", #"Action",
self.strReponse, #"Data",
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"avc" object:self userInfo:dictUserInfo];
}
Edit: I don't need to save these operations as I am storing the incoming request in a mutable array and which just create a new OQ once they have been appended. I just want to make sure they are cancelled and not leaving the app in a fragile state.

Why won't this loop exit

My assumption is that the operations are running asynchronously on a separate thread, but the loop never exits, so something is not as I assumed.
/**
Checks if we can communicate with the APIs
#result YES if the network is available and all of the registered APIs are responsive
*/
- (BOOL)apisAvailable
{
// Check network reachability
if (!_connectionAvailable) {
return NO;
}
// Check API server response
NSMutableSet *activeOperations = [[NSMutableSet alloc] init];
__block NSInteger successfulRequests = 0;
__block NSInteger failedRequests = 0;
for (AFHTTPClient *httpClient in _httpClients) {
// Send heart beat request
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Server returned good response
successfulRequests += 1;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Server returned bad response
failedRequests += 1;
}];
[operation start];
[activeOperations addObject:operation];
}
// Wait for heart beat requests to finish
while (_httpClients.count > (successfulRequests + failedRequests)) {
// Wait for each operation to finish, one at a time
//usleep(150);
[NSThread sleepForTimeInterval:0.150];
}
// Check final results
if (failedRequests > 0) {
return NO;
}
return YES;
}
A few suggestions:
Never check reachability to determine if a request will succeed. You should try the request; only if it fails should you consult reachability to try and get a best guess as to why. Reachability makes no guarantee about whether a request will fail or succeed.
Is this method called on the main thread? Even if you fixed the problem with the requests never completing, it will block the UI the entire time your network requests are running. Since these requests can take potentially a long time, this is a bad experience for the user as well as something the OS will kill your app for if it happens at the wrong time (e.g. at launch).
Looping while calling sleep or equivalent is wasteful of CPU resources and memory, as well as prevents the thread's runloop from servicing any timers, event handler or callbacks. (Which is probably why the networking completion blocks never get to run.) If you can avoid blocking a thread, you should. In addition, Cocoa will very often be unhappy if you do this on an NSThread you didn't create yourself.
I see two options:
Use dispatch_groups to wait for all of your requests to finish. Instead of blocking your calling thread, you should instead take a completion block to call when you're done. So, instead of returning a BOOL, take a completion block which takes a BOOL. Something like - (void)determineIfAPIIsAvailable:(void(^)(BOOL))completionBlock;
Get rid of this method altogether. What are you using this method for? It's almost certainly a better idea to just try to use your API and report appropriate errors to the user when things fail rather than to try to guess if a request to the API will succeed beforehand.
I believe the issue is that I was not using locking to increment the counters so the while loop would never evaluate to true.
I was able to get it working by only looking for a fail count greater than 0 that way as long as it was incremented by any of the request callback blocks then I know what to do.
I just so happen to have switched to [NSOperationQueue waitUntilAllOperationsAreFinished].
Final code:
/**
Checks if we can communicate with the APIs
#result YES if the network is available and all of the registered APIs are responsive
*/
- (BOOL)apisAvailable
{
// Check network reachability
if (!_connectionAvailable) {
return NO;
}
// Check API server response
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
__block NSInteger failedRequests = 0;
for (AFHTTPClient *httpClient in _httpClients) {
// Send heart beat request
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:#"" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Server returned good response
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Server returned bad response
failedRequests += 1;
}];
[operationQueue addOperation:operation];
}
// Wait for heart beat requests to finish
[operationQueue waitUntilAllOperationsAreFinished];
// Check final results
if (failedRequests > 0) {
return NO;
}
return YES;
}

Waiting on asynchronous methods using NSCondition

I am downloading four plist files asynchronously over the internet. I need to wait until all four files are downloaded, until I either on the first run, push a UIViewController, or on all subsequent runs, refresh the data, and reload all my UITableViews.
On the first run, everything works perfectly. When refreshing though, all four url requests are called, and started, but never call their completion or failure blocks, and the UI freezes. Which is odd since I preform all operations in a background thread. I have not been able to figure out why this is happening.
The first load and the refresh methods call the four "update" methods in the same way, and use NSCondition in the same way.
For the first run:
- (void)loadContentForProgram:(NSString *)programPath
{
NSLog(#"Start Load Program");
AppDelegate *myDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
hud = [[MBProgressHUD alloc] initWithView:myDelegate.window];
[myDelegate.window addSubview:hud];
hud.labelText = #"Loading...";
hud.detailsLabelText = #"Loading Data";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Do stuff here to load data from files
//Update From online files
hud.detailsLabelText = #"Updating Live Data";
resultLock = NO;
progressLock = NO;
recallLock = NO;
stageLock = NO;
condition = [[NSCondition alloc] init];
[condition lock];
[self updateCurrentCompsText];
[self updateCompetitionResults];
[self updateCompetitionRecalls];
[self updateCompetitionProgress];
while (!resultLock) {
[condition wait];
}
NSLog(#"Unlock");
while (!stageLock) {
[condition wait];
}
NSLog(#"Unlock");
while (!recallLock) {
[condition wait];
}
NSLog(#"Unlock");
while (!progressLock) {
[condition wait];
}
NSLog(#"Unlock");
[condition unlock];
updateInProgress = NO;
//Reset Refresh controls and table views
self.refreshControlsArray = [[NSMutableArray alloc] init];
self.tableViewsArray = [[NSMutableArray alloc] init];
NSLog(#"Finished Loading Program");
[[NSNotificationCenter defaultCenter] postNotificationName:#"WMSOFinishedLoadingProgramData" object:nil]; //Pushes view controller
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:myDelegate.window animated:YES];
});
});
}
When refreshing data:
- (void)updateProgramContent
{
if (!updateInProgress) {
updateInProgress = YES;
for (int i = 0; i < self.refreshControlsArray.count; i++) {
if (!((UIRefreshControl *)self.refreshControlsArray[i]).refreshing) {
[self.refreshControlsArray[i] beginRefreshing];
[self.tableViewsArray[i] setContentOffset:CGPointMake(0.0, 0.0) animated:YES];
}
}
resultLock = NO;
stageLock = NO;
recallLock = NO;
progressLock = NO;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
condition = [[NSCondition alloc] init];
[condition lock];
[self updateCompetitionProgress];
[self updateCompetitionRecalls];
[self updateCompetitionResults];
[self updateCurrentCompsText];
while (!resultLock) {
[condition wait];
}
NSLog(#"Unlock");
while (!stageLock) {
[condition wait];
}
NSLog(#"Unlock");
while (!recallLock) {
[condition wait];
}
NSLog(#"Unlock");
while (!progressLock) {
[condition wait];
}
NSLog(#"Unlock");
[condition unlock];
});
for (int i = 0; i < self.refreshControlsArray.count; i++) {
[self.refreshControlsArray[i] performSelector:#selector(endRefreshing) withObject:nil afterDelay:1.0];
[self.tableViewsArray[i] performSelector:#selector(reloadData) withObject:nil afterDelay:1.0];
}
updateInProgress = NO;
}
}
The block below that appears in each loading method above, corresponds to a method that will download and update a specific piece of data.
[self updateCompetitionProgress];
[self updateCompetitionRecalls];
[self updateCompetitionResults];
[self updateCurrentCompsText];
which runs:
- (void)updateCompetitionResults
{
__block NSDictionary *competitionResultsData = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"Some URL",[self.programName stringByReplacingOccurrencesOfString:#" " withString:#"%20"]]] cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:20.0];
AFPropertyListRequestOperation *operation = [AFPropertyListRequestOperation propertyListRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList) {
competitionResultsData = (NSDictionary *)propertyList;
[competitionResultsData writeToFile:[#"SOME LOCAL PATH"] atomically:NO];
[self updateCompetitionResultsWithDictionary:competitionResultsData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList) {
competitionResultsData = [NSDictionary dictionaryWithContentsOfFile:[#"SOME LOCAL PATH"]];
NSLog(#"Failed to retreive competition results: %#", error);
[self updateCompetitionResultsWithDictionary:competitionResultsData];
}];
[operation start];
}
and the completion and failure blocks call the same method to update the data
- (void)updateCompetitionResultsWithDictionary:(NSDictionary *)competitionResultsData
{
//Do Stuff with the data here
resultLock = YES;
[condition signal];
}
So, Why does this work on the first run, but not any of the subsequent runs?
As I mentioned in my comments, above, the most obvious problem is that you're invoking methods that use condition before you initialize condition. Make sure initialize condition before you start calling updateCompetitionResults, etc.
In terms of a more radical change, I might suggest retiring NSCondition altogether, and use operation queues:
I might use NSOperationQueue (or you can use dispatch groups, too, if you want, but I like the operation queue's ability to configure how many concurrent operations you can operate ... also if you get to a point that you want to cancel operations, I think NSOperationQueue offers some nice features there, too). You can then define each download and processing as a separate NSOperation (each of the downloads should happen synchronously, because they're running in an operation queue, you get the benefits of asynchronous operations, but you can kick off the post-processing immediately after the download is done). You then just queue them up to run asynchronously, but define a final operation which is dependent upon the other four will kick off as soon as the four downloads are done. (By the way, I use NSBlockOperation which provides block-functionality for NSOperation objects, but you can do it any way you want.)
And whereas your updateProgramContent might download asynchronously, it processes the four downloaded files sequentially, one after another. Thus, if the first download takes a while to download, it will hold up the post-processing of the others. Instead, I like to encapsulate both the downloading and the post processing of each of the four plist files in a single NSOperation, each. Thus, we enjoy maximal concurrency of not only the downloading, but the post-processing, too.
Rather than using the AFNetworking (which I'm generally a big fan of) plist-related method, I might be inclined to use NSDictionary and NSArray features that allow you to download a plist from the web and load them into the appropriate structure. These dictionaryWithContentsOfURL and arrayWithContentsOfURL run synchronously, but because we're doing this in a background operation, everything runs asynchronously like you want. This also bypasses the saving them to files. If you wanted them saved to files in your Documents directory, you can do that easily, too. Clearly, if you're doing something sophisticated in your downloading of the plist files (e.g. your server is engaging in some challenge-response authentication), you can't use the convenient NSDictionary and NSArray methods. But if you don't need all of that, the simple NSDictionary and NSArray methods, ___WithContentsOfURL make life pretty simple.
Pulling this all together, it might look like:
#interface ViewController ()
#property (nonatomic, strong) NSArray *competitions;
#property (nonatomic, strong) NSDictionary *competitionResults;
#property (nonatomic, strong) NSDictionary *competitionRecalls;
#property (nonatomic, strong) NSDictionary *competitionProgress;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self transfer];
}
- (void)allTransfersComplete
{
BOOL success;
if (self.competitions == nil)
{
success = FALSE;
NSLog(#"Unable to download competitions");
}
if (self.competitionResults == nil)
{
success = FALSE;
NSLog(#"Unable to download results");
}
if (self.competitionRecalls == nil)
{
success = FALSE;
NSLog(#"Unable to download recalls");
}
if (self.competitionProgress == nil)
{
success = FALSE;
NSLog(#"Unable to download progress");
}
if (success)
{
NSLog(#"all done successfully");
}
else
{
NSLog(#"one or more failed");
}
}
- (void)transfer
{
NSURL *baseUrl = [NSURL URLWithString:#"http://insert.your.base.url.here/competitions"];
NSURL *competitionsUrl = [baseUrl URLByAppendingPathComponent:#"competitions.plist"];
NSURL *competitionResultsUrl = [baseUrl URLByAppendingPathComponent:#"competitionresults.plist"];
NSURL *competitionRecallsUrl = [baseUrl URLByAppendingPathComponent:#"competitionrecalls.plist"];
NSURL *competitionProgressUrl = [baseUrl URLByAppendingPathComponent:#"competitionprogress.plist"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4; // if your server doesn't like four concurrent requests, you can ratchet this back to whatever you want
// create operation that will be called when we're all done
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// any stuff that can be done in background should be done here
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// any user interface stuff should be done here; I've just put this in a separate method so this method doesn't get too unwieldy
[self allTransfersComplete];
}];
}];
// a variable that we'll use as we create our four download/process operations
NSBlockOperation *operation;
// create competitions operation
operation = [NSBlockOperation blockOperationWithBlock:^{
// download the competitions and load it into the ivar
//
// note, if you *really* want to download this to a file, you can
// do that when the download is done
self.competitions = [NSArray arrayWithContentsOfURL:competitionsUrl];
// if you wanted to do any post-processing of the download
// you could do it here.
NSLog(#"competitions = %#", self.competitions);
}];
[completionOperation addDependency:operation];
// create results operation
operation = [NSBlockOperation blockOperationWithBlock:^{
self.competitionResults = [NSDictionary dictionaryWithContentsOfURL:competitionResultsUrl];
NSLog(#"competitionResults = %#", self.competitionResults);
}];
[completionOperation addDependency:operation];
// create recalls operation
operation = [NSBlockOperation blockOperationWithBlock:^{
self.competitionRecalls = [NSDictionary dictionaryWithContentsOfURL:competitionRecallsUrl];
NSLog(#"competitionRecalls = %#", self.competitionRecalls);
}];
[completionOperation addDependency:operation];
// create progress operation
operation = [NSBlockOperation blockOperationWithBlock:^{
self.competitionProgress = [NSDictionary dictionaryWithContentsOfURL:competitionProgressUrl];
NSLog(#"competitionProgress = %#", self.competitionProgress);
}];
[completionOperation addDependency:operation];
// queue the completion operation (which is dependent upon the other four)
[queue addOperation:completionOperation];
// now queue the four download and processing operations
[queue addOperations:completionOperation.dependencies waitUntilFinished:NO];
}
#end
Now, I don't know which of your plist's are arrays and which are dictionaries (in my example, I made competitions an array and the rest were dictionaries keyed by the competition id), but hopefully you get the idea of what I was shooting for. Maximize concurrency, eliminate NSCondition logic, really make the most of NSOperationQueue, etc.
This may be all to much to take in, but I only mention it as an alternative to NSCondition. If your current technique works, that's great. But the above outlines how I would tackle a challenge like this.

Resources