I am using NSOperationQueues in my application for downloading the data from the API and inserting into the local database. I am using one custom NSOpertion and adding to the NSOpertionQueue and executing for every request. When I perform two multiple requests with two different NSOperationQueues, they are not executing concurrently. I want know how to execute NSOperationQueues concurrently.
//Request for master tables data.
[RequestHandler getRequestForMasterTablesData:self];
[RequestHandler postProspectListRequest:self];
/**
* #brief ProspectList API: Gives the list of Prospects.
*/
+ (void)postProspectListRequest:(UIViewController*)lViewController {
__weak ProspectListViewController *weakSelf = (ProspectListViewController*)lViewController;
/**********************ONLINE MODE**********************/
if ([self isNetworkAvailable]) {
NSString *prospectListUrl = [NSString stringWithFormat:#"%#%#/%#",[[DataBaseManipulator sharedInstance] getCompanyURLFromUserDefaults],PROSPECTS,PROSPECTSLIST];
RequestOperationManager *op = [[RequestOperationManager alloc] initWithGetRequest:prospectListUrl];
__weak RequestOperationManager *weakOperation = op;
op.completionBlock = ^{
__strong RequestOperationManager *strong = weakOperation;
dispatch_async(dispatch_get_main_queue(), ^{
[[DataBaseManipulator sharedInstance] insertDataIntoProspectListDB:[strong.resultsDictionary valueForKey:#"prospects"]];
});
};
NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
[networkQueue setMaxConcurrentOperationCount:1];
[networkQueue addOperation:op];
}
}
+ (void)getRequestForMasterTablesData:(UIViewController *)viewController {
if (![self isNetworkAvailable]) {
[[NetworkMonitor instance]displayNetworkMonitorAlert];
return;
}
NSString *masterTablesUrl = [NSString stringWithFormat:#"%#%#",[[DataBaseManipulator sharedInstance] getCompanyURLFromUserDefaults],MASTERTABLESDATA];
__weak LoginViewController *weakSelf = (LoginViewController*)viewController;
[[GenricUI instance] createLoadView];
RequestOperationManager *op = [[RequestOperationManager alloc] initWithGetRequest:masterTablesUrl];
__weak RequestOperationManager *weakOperation = op;
op.completionBlock = ^{
__strong RequestOperationManager *strong = weakOperation;
dispatch_async(dispatch_get_main_queue(), ^{
[[DataBaseManipulator sharedInstance] insertMasterDataIntoSalesItemTable:strong.resultsDictionary];
[[GenricUI instance] removeLoadView];
[weakSelf parseMasterTableResponse:strong.resultsDictionary];
});
};
NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
[networkQueue setMaxConcurrentOperationCount:1];
[networkQueue addOperation:op];
}
Iam calling the above two methods at a time but they are not executing concurrently. How to execute the above two methods concurrently.
If you want concurrent execution, you shouldn't have a call
[networkQueue setMaxConcurrentOperationCount:1];
On the other hand, this code
NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
[networkQueue setMaxConcurrentOperationCount:1];
[networkQueue addOperation:op];
is rather useless. You have a queue, add a single operation, and that's it. This doesn't achieve anything. If you want concurrent execution, create only one NSOperationQueue, keep hold of it to reuse it, and don't disable concurrent operations on it.
By the way, you seem to assume that once you have a network connection, a call to your server will succeed. That is to say the least rather optimistic.
Related
I've been looking for a way to pass results for chained NSOperation. For example, lets assume we have 3 operations chained:
Operation1 to download JSON data from server
Operation2 to parse & model JSON received
Operation3 to download user images
So Op3 would be dependent on Op2, which is dependent on Op1. But I'm looking for way to pass results from Op1 -> Op2, then from Op2 -> Op3 as:
[operation1 startWithURL:url];
[operation2 parseJSONfromOp1IntoModel:JSONData];
[operation3 downloadUserImagesForUser: UserModelObject];
and nesting blocks doesn't seem to be a clean readable solution, any idea?
If you want to chain operations, but don't like the nesting, you can use NSOperation subclasses, and then define your own completion handlers:
DownloadOperation *downloadOperation = [[DownloadOperation alloc] initWithURL:url];
ParseOperation *parseOperation = [[ParseOperation alloc] init];
DownloadImagesOperation *downloadImagesOperation = [[DownloadImagesOperation alloc] init];
downloadOperation.downloadCompletionHandler = ^(NSData *data, NSError *error) {
if (error != nil) {
NSLog(#"%#", error);
return;
}
parseOperation.data = data;
[queue addOperation:parseOperation];
};
parseOperation.parseCompletionHandler = ^(NSDictionary *dictionary, NSError *error) {
if (error != nil) {
NSLog(#"%#", error);
return;
}
NSArray *images = ...;
downloadImagesOperation.images = images;
[queue addOperation:downloadImagesOperation];
};
[queue addOperation:downloadOperation];
Frankly, though, I'm not sure that's any more intuitive than the nested approach:
DownloadOperation *downloadOperation = [[DownloadOperation alloc] initWithURL:url downloadCompletionHandler:^(NSData *data, NSError *error) {
if (error != nil) {
NSLog(#"%#", error);
return;
}
ParseOperation *parseOperation = [[ParseOperation alloc] initWithURL:data parseCompletionHandler:^(NSDictionary *dictionary, NSError *error) {
if (error != nil) {
NSLog(#"%#", error);
return;
}
NSArray *images = ...
DownloadImagesOperation *downloadImagesOperation = [[DownloadImagesOperation alloc] initWithImages:images imageDownloadCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"%#", error);
return;
}
// everything OK
}];
[queue addOperation:downloadImagesOperation];
}];
[queue addOperation:parseOperation];
}];
[queue addOperation:downloadOperation];
By the way, the above assumes that you're familiar with subclassing NSOperation, especially the subtleties of creating an asynchronous NSOperation subclass (and doing all of the necessary KVO). If you need examples of how that's done, let me know.
Creating chained operations:
Create the Op2 from within the completion block of Op1, then use delegation or something similar to set the dependency on the newly created operation. You can use this pattern to chain as many as you want. To pass the result in the completion block, you cannot use completionBlock that is on NSOperation. You will need to define your own (like I did with almostFinished) in order to pass the result through.
- (void)someMethod {
Operation1 *operation1 = [[Operation1 alloc] init];
operation1.almostFinished = ^(id op1Result) {
Operation2 *operation2 = [[Operation2 alloc] initWithResultFromOp1: op1Result];
operation2.almostFinished = ^(id op2Result) {
Operation3 *operation3 = [[Operation3 alloc] initWithResultFromOp2:op2Result];
operation3.completionBlock = ^{
NSLog(#"Operations 1 and 2 waited on me, but now we're all finished!!!);
};
[operation2 addDependency:operation3];
[queue addOperation:operation3];
};
[operation1 addDependency:operation2];
[queue addOperation:operation2];
};
[queue addOperation:operation1];
}
Custom Subclass
You will need to subclass NSOperation for this to work. As I mentioned, you need to define your own completion block AND make sure that completion block is called before the operation is truly finished so that you can add the dependency. Instead of adding the dependency in the new completion block, you could add it in a different block or delegate method. This way kept my example concise.
#interface Operation: NSOperation {
#property (nonatomic, copy) void (^almostFinished)(id result);
#end
#implementation Operation {
//...
- (void)main {
//...
// Call here to allow to add dependencies and new ops
self.almostFinished(result);
// Finish the op
[self willChangeValueForKey:#"isFinished"];
// repeat for isExecuting and do whatever else
[self didChangeValueForKey:#"isFinished"];
}
#end
EDIT: This isn't the most readable thing, but it contains all the code in one method. If you want to get fancy, then place things out in delegate methods or get creative with how you define these things.
hi guys im very frustrated because i want to improve a code but i'm not getting good results this is my piece of code
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock: ^{
value1 = [self getDiferences:0.0 finx:width iniy:0.0 finy:cuartoheith image:imagen1 imagetoComapare:imagen2];
}];
[queue addOperation:blockOperation1];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock: ^{
value2 = [self getDiferences:0.0 finx:width iniy:0.0 finy:cuartoheith image:imagen1 imagetoComapare:imagen2];
}];
[queue addOperation:blockOperation2];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock: ^{
value3 = [self getDiferences:0.0 finx:width iniy:0.0 finy:cuartoheith image:imagen1 imagetoComapare:imagen2];
}];
[queue addOperation:blockOperation3];
NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock: ^{
value4 = [self getDiferences:0.0 finx:width iniy:0.0 finy:cuartoheith image:imagen1 imagetoComapare:imagen2];
}];
[queue addOperation:blockOperation4];
i want use this values outside the NSBlockOperation like this valuetotal=value1+value2+value3+value4; please help or help with a better solution
Add another operation which is dependent on your other operations (using addDependency:), and add your code there. Queue this operation. It will wait for all others to finish, and then use their output.
For example,
NSBlockOperation *blockOperationFinal = [NSBlockOperation blockOperationWithBlock: ^{
valueTotal = value1 + value2 + value3 + value4;
}];
[blockOperationFinal addDependency:blockOperation1];
[blockOperationFinal addDependency:blockOperation2];
[blockOperationFinal addDependency:blockOperation3];
[blockOperationFinal addDependency:blockOperation4];
[queue addOperation:blockOperationFinal];
one block operation is enough, cause it is able to add more blocks, and execute concurrently, meanwhile provide a block to trigger when all blocks are completed, like this:
NSMutableArray *array = [[NSMutableArray alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[array addObject:#"op1"];
}];
[operation addExecutionBlock:^{
[array addObject:#"op2"];
}];
[[[NSOperationQueue alloc] init] addOperation:operation];
[operation setCompletionBlock:^{
NSLog(#"array:%#", array);
}];
I am new to blocks and am trying to figure out how to wait for the block to finish before performing my action (in this case a nslog) So how can I wait till the block is done before performing this nslog in the code below: NSLog(#"convertedPhotos::%#",convertedImages);
convertedImages = [[NSMutableArray alloc] init];
for (NSDictionary *photo in photos) {
// photo is a dictionary containing a "caption" and a "urlRep"
[photoUrls addObject:photo[#"urlRep"]];
}
if (photoUrls.count) {
for (id photos in photoUrls){
NSString *urlString = photos;
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
[jsonWithPhotos setObject:convertedImages forKey:#"photo64"];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonWithPhotos
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"json::%#",jsonString);
}
}];
}
}
else {
NSLog(#"where are my urls?");
}
NSLog(#"convertedPhotos::%#",convertedImages);
}
}
this method/block is called from above
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
[convertedImages addObject:base64];
// NSLog(#"converted::%#",convertedImages);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
I would use NSOperationQueue (or dispatch queue) and NSCondition (or dispatch group) to wait for operations to complete. It is also important to wrap blocks in #autoreleasepool to flush memory once you do not need it if you work with memory consuming objects like NSData.
example:
// create results array
__block NSMutableArray* results = [NSMutableArray new];
// create serial queue
dispatch_queue_t queue = dispatch_queue_create("myQueue", 0);
for(NSInteger i = 0; i < 10; i++) {
// enqueue operation in queue
dispatch_async(queue, ^{
// create semaphore
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// do something async, I do use another dispatch_queue for example
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// wrap in autoreleasepool to release memory upon completion
// in your case wrap the resultBlock in autoreleasepool
#autoreleasepool {
// here for example the nested operation sleeps for two seconds
sleep(2);
// add the operation result to array
// I construct an array of strings for example
[results addObject:[NSString stringWithFormat:#"Operation %d has finished.", i]];
// signal that nested async operation completed
// to wake up dispatch_semaphore_wait below
dispatch_semaphore_signal(sema);
}
});
// wait until the nested async operation signals that its finished
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(#"Finished single operation.");
});
}
// will be called once all operations complete
dispatch_async(queue, ^{
NSLog(#"Finished all jobs.");
NSLog(#"Results: %#", results);
});
For any non-main queue use semaphores
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// some serious stuff here
...
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
In case you want to wait for async task execution being in the main queue - you wouldn't probably want to block it while waiting for a semaphore.
I use this construction which doesn't freeze the UI for the main queue only.
__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// some serious stuff here
...
flag = YES;
});
// Run until 'flag' is not flagged (wait for the completion block to finish executing
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
Your best option is not to use blocks directly. Instead, create instances of NSBlockOperation and add them to an operation queue. Then create one more NSBlockOperation and make it dependent upon all of the other operations. This ensures that the last operation is only run after all others are completed and allows you to control how many operations will run at any one time.
If you have nested block calls, or some API that you can't change to enable this then you can still do it if you create an NSOperation subclass such that the operation does not complete until all of the asynchronous operations are complete. Take a look at dribin.org concurrent operations.
I want to achieve the following task using NSThread
I've a main thread and three (3) workerThread T1, T2, T3. All these started at the same time from main thread, Main thread has an int size variable. Now I want to synchronize all three threads in a way they, when my each of the above threads will execute, it will print the following:
//in main thread
- (void) mainFunction{
size = 0;
NSThread* T1 = [[NSThread alloc] initWithTarget:self
selector:#selector(workerThread:)
object:#"T1"];
[T1 start];
NSThread* T2 = [[NSThread alloc] initWithTarget:self
selector:#selector(workerThread:)
object:#"T2"];
[T2 start];
NSThread* T3 = [[NSThread alloc] initWithTarget:self
selector:#selector(workerThread:)
object:#"T3"];
[T3 start];
}
// worker thread
- (void) workerThread:(id)obj{
size++;
NSLog(#"Thread:%#--------Size:%d,obj,size)
}
I want following output:
Thread:T1-------Size:1
Thread:T2-------Size:2
Thread:T3-------Size:3
Thread:T1-------Size:4
Thread:T2-------Size:5
Thread:T3-------Size:6
Thread:T1-------Size:7
Thread:T2-------Size:8
Thread:T3-------Size:9
and return the control back to main thread when size=10
A couple of thoughts:
You say "return control back to main thread when size=10". That doesn't quite make sense. The main thread never "lost" control (as these threads are happening concurrently). Perhaps you wanted something to happen on the main thread when this situation arose?
You're not having the workerThread method do any looping, so as you've written it, each thread will do this once and then quit. You probably need to add some form of loop here.
Even if you added looping, your desired output suggests that you're assuming a particular sequence of actions that would take place, namely that these three threads will run in order (but you have no such assurances). If you needed that behavior, you'd set up a series of semaphores by which you could have one thread waiting for a signal to be sent by another.
You should be careful when updating a variable from different threads. See the Synchronization section of the Threading Programming Guide. It's simplified when dealing with a fundamental data type like your counter (just make sure you declare it as atomic). But in more substantive scenarios, you'll want to employ some synchronization technique such as #synchronized, locks, dedicated custom serial queue, etc.
In general, if you're using threads (but not necessary if using queues), you should be creating an autorelease pool for your thread.
Anyway, with these observations aside, you might have something like the following, which (a) has #autoreleasepool; (b) loops; and (c) uses a lock to make sure that the various threads synchronize their interactions with the size variable:
- (void) workerThread:(id)obj
{
#autoreleasepool {
BOOL done = NO;
while (!done) {
[self.lock lock];
if (size < 9) {
size++;
NSLog(#"Thread:%#--------Size:%d", obj, size);
}
else
{
done = YES;
}
[self.lock unlock];
// perhaps you're doing something time consuming here...
}
}
}
This assumes you have NSLock property, called lock:
#property (nonatomic, strong) NSLock *lock;
Which you created before initiating your thread test:
- (void) threadTest
{
size = 0;
self.lock = [[NSLock alloc] init];
NSThread* T1 = [[NSThread alloc] initWithTarget:self selector:#selector(workerThread:) object:#"T1"];
[T1 start];
NSThread* T2 = [[NSThread alloc] initWithTarget:self selector:#selector(workerThread:) object:#"T2"];
[T2 start];
NSThread* T3 = [[NSThread alloc] initWithTarget:self selector:#selector(workerThread:) object:#"T3"];
[T3 start];
}
Having said all this, you started this with "return control back to the main thread". As I said earlier, there's really no need to do that because in your example, your app's main thread never yielded control
For controlling relationships between tasks happening on different threads, I might suggest using GCD or operation queues. They're easier to use and have better mechanism for controlling dependencies between various tasks/operations (see Concurrency Programming Guide).
For example, consider the operation-based equivalent to your above workerThread method (identical, but no autorelease pool is needed):
- (void) operationMethod:(id)obj
{
BOOL done = NO;
while (!done) {
[self.lock lock];
if (size < 9) {
size++;
NSLog(#"Operation:%#--------Size:%d", obj, size);
}
else
{
done = YES;
}
[self.lock unlock];
// perhaps you're doing something time consuming here...
}
}
You could then create three operations (which probably will run on three threads) and wait for the result, like so:
- (void) operationTestWait
{
size = 0;
self.lock = [[NSLock alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationMethod:) object:#"Op1"];
NSOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationMethod:) object:#"Op2"];
NSOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationMethod:) object:#"Op3"];
[queue addOperations:#[op1, op2, op3] waitUntilFinished:YES];
// do here whatever should happen when the operations are done
}
In this case, the main thread will wait for these three operations to finish.
Or, better, if these tasks take more than a few milliseconds, you should not have the main queue wait (since you should never block the main queue), but rather, you should simply define what you want the main queue to do when the three operations are done:
- (void) operationTest
{
size = 0;
self.lock = [[NSLock alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationMethod:) object:#"Op1"];
NSOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationMethod:) object:#"Op2"];
NSOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(operationMethod:) object:#"Op3"];
NSOperation *completion = [NSBlockOperation blockOperationWithBlock:^{
// if you want this to do something on the main queue, then have this add it to the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do here whatever should happen when the operations are done
}];
}];
// define this completion operation to be dependent upon the above three operations
[completion addDependency:op1];
[completion addDependency:op2];
[completion addDependency:op3];
// now add all of them, but don't wait until finished;
// but the completion operation will only start when its dependencies
// are resolved
[queue addOperations:#[op1, op2, op3, completion] waitUntilFinished:NO];
}
Forgive the long-winded answer. If you can give us a more practical example of what these various threads will be doing and we can provide better counsel on how to best tackle it. But, in general, operation queues or dispatch queues will probably be more efficient way to tackle most concurrency related challenges.
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.