I have to sync a bunch of information from my RestAPI. I must do 6 RestAPI calls to complete work. I designed API calls with Blocks, and return NSError if there is any.
3 of these calls should to execute nested because the first call gives information to others and allows the execution while the other 3 calls can run independently.
Due to improve network performance, I designed my synchronization call as following:
1 NSBlockOperation that contains the first nested 3 blocks;
1 NSBlockOperation that contains other three blocks;
1 NSBlockOperation that I use as "semphore" and tells me when all work done.
Last NSBlockOperation has dependency to previous two NSBlockOperation.
I also have a NSOperationQueue that contains all three NSBlockOperation where the semaphore NSBlockOperation is added as last in the queue.
The result that I would to achieve is: first two blocks called Concurrently and when their work finish, the semaphore NSBlockOperation is called and returns controls to User providing UIAlertMessage.
The result isn't that previously explained: controls are returned without waiting the end of syncAllBlocksInformation block.
Below the code that contains NSBlockOperation:
-(void)syncAllBlocksInformation:(void(^)(NSError *error))completion{
__block NSError *blockError = nil;
NSOperation *syncUserInfoOperation = [NSBlockOperation blockOperationWithBlock:^{
[dataSync syncUserInfo:tfMail.text password:tfPassword.text completion:^(NSError *error, NSNumber *idUser) {
if(!error){
[dataSync syncUserfilesInfo:idUser completion:^(NSError *error) {
if(!error){
[dataSync syncUserBookings:^(NSError *error) {
if(error){
blockError = error;
}
}];
}
else{
blockError = error;
}
}];
}
else{
blockError = error;
}
}];
}];
NSBlockOperation *otherSyncOperations = [NSBlockOperation blockOperationWithBlock:^{
[dataSync syncNewsInfo:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
[otherSyncOperations addExecutionBlock:^{
[dataSync syncLocationsInfo:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
[otherSyncOperations addExecutionBlock:^{
[dataSync syncExoticAnimalTypesAndAnimals:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"END");
}];
[completionOperation setCompletionBlock:^{
NSLog(#"Syc isEx %i",syncUserInfoOperation.isExecuting);
NSLog(#"other isEx %i",otherSyncOperations.isExecuting);
completion(blockError);
}];
NSOperationQueue *opQueue = [NSOperationQueue new];
[completionOperation addDependency:syncUserInfoOperation];
[completionOperation addDependency:otherSyncOperations];
[opQueue addOperation:syncUserInfoOperation];
[opQueue addOperation:otherSyncOperations];
[opQueue addOperation:completionOperation];
}
And here, code that calls above block:
-(IBAction)login:(id)sender{
[self dismissKeyboardOpened:nil];
hud=[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[hud setLabelText:NSLocalizedString(#"login_hud_message", login_hud_message )];
[hud setMode:MBProgressHUDModeIndeterminate];
[self showHudAndNetworkActivity:YES];
[self syncAllBlocksInformation:^(NSError *error) {
[self showHudAndNetworkActivity:NO];
if(!error){
NSLog(#"End LOGIN");
[self showAlert:#"Login" message:#"Login OK" dismiss:YES];
}
else{
[self showAlert:#"Error" message:#"Login NO" dismiss:NO];
}
}];
}
What's wrong ?
The issue is that NSBlockOperation is for synchronous blocks. It will be finished as soon as its block(s) have finished executing. If its block(s) fire off asynchronous methods, those will run independently.
For example, when your syncUserInfoOperation's block is executed, it fires off [dataSync syncUserInfo:...] and then considers itself done; it doesn't wait for any of the completion handlers to fire, or anything like that.
A good solution to this is to create your own NSOperation subclasses. You'd probably want to create one for each of your data sync types to make it easier to setup dependencies, etc., but that's up to you. You can read all about how to do that here (be sure to read the section on "Configuring Operations for Concurrent Execution").
You could also make a generic NSOperation subclass that takes a block that can be run asynchronously. The main issue with that is it makes it much harder to handle things like canceling the operation, which you probably still want.
Related
I am trying to run AFURLConnectionOperation seen below on the currentQueue as I want to keep my main thread free for user interatoin, however nothing happens when I call mainQeue.
However if I call the same AFURLConnectionOperation on mainQueue it works perfectly.
Pleas see following code
// Send Batch
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"%lu of %lu complete", (unsigned long)numberOfFinishedOperations, (unsigned long)totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
// check batch response
NSError *error;
for (AFHTTPRequestOperation *op in operations) {
if (op.isCancelled){
return ;
}
if (op.responseObject){
// Current JSON Batch complete
NSMutableArray *jsonObject = [NSJSONSerialization JSONObjectWithData:op.request.HTTPBody options:kNilOptions error:&error];
// Update sent_flag using current chunk
[[SyncModel sharedInstance] updateSentFlag:jsonObject];
}
if (op.error){
error = op.error;
NSLog(#"Error == %#", error);
}
}
}];
Then finally I call one or the other of the following code
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO]; // this works
[[NSOperationQueue currentQueue] addOperations:operations waitUntilFinished:NO]; // this dose not work
The reason is
You can use this method from within a running operation object to get a reference to the operation queue that started it. Calling this method from outside the context of a running operation typically results in nil being returned.
So,I guess,if you log [NSOperationQueue currentQueue],it is nil
If you want a new queue,use
[[NSOperationQueue alloc] init];
After adding the operation on queue, if the operation doesn't start eventually then there are two ways to get them executed.
Using wait block, for example during unit test using XCTest framework, use
XCTestExpectation *expectation1 = [self expectationWithDescription:#"ExtractColorsInternal function call on NSOperationQueue"];
dispatch_async(dispatch_get_main_queue(), ^{
[expectation1 fulfill];
});
[self waitForExpectationsWithTimeout:1000 handler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error: %#", error.localizedDescription);
}
}];
call CFRunLoopRun(), which would execute the present operation in current queue succesfully
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.
I have started to explore the new XCTest APIs for asynchronous and performance testing. In isolation, the Apple examples from WWMC work well, but I have been unable to figure out how to combine them. The best I have been able to come up with is the following, but I receive the following error when it runs:
API violation - call made to wait without any expectations having been set.
XCTestExpectation *clsQueryReturnedExpectation = [self expectationWithDescription:#"clsQuery returned"];
PFCLSClient *theClient = [[PFCLSClient alloc] init];
[self measureMetrics:#[XCTPerformanceMetric_WallClockTime] automaticallyStartMeasuring:YES forBlock: ^{
[theClient getStructureOfType:clsImageTypeSVG ForID:idString success: ^(NSDictionary *structureInfo) {
[clsQueryReturnedExpectation fulfill];
} failure: ^(NSError *error) {
XCTFail();
[clsQueryReturnedExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler: ^(NSError *error) {
[self stopMeasuring];
}];
}];
Has anyone been able to accomplish something similar?
Thx
With some help from Apple, I have a solution. Silly oversight on my part as this is very easy to solve. To get to work, all you need to do is put the creating of the expectation object (clsQueryReturnedExpectation) inside the measureMetrics block so it is created afresh each time the performance test is run.
PFCLSClient *theClient = [[PFCLSClient alloc] init];
[self measureMetrics:#[XCTPerformanceMetric_WallClockTime] automaticallyStartMeasuring:YES forBlock: ^{
XCTestExpectation *clsQueryReturnedExpectation = [self expectationWithDescription:#"clsQuery returned"];
[theClient getStructureOfType:clsImageTypeSVG ForID:idString success: ^(NSDictionary *structureInfo) {
[clsQueryReturnedExpectation fulfill];
} failure: ^(NSError *error) {
XCTFail();
[clsQueryReturnedExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler: ^(NSError *error) {
[self stopMeasuring];
}];
}];
I'm having trouble with semaphore.
I have a serie of blocks and I want a block is executed just when the previous one has been finished its work.
I red that I have to play with gcd semaphore but the app stop working at the point signed in the code and it never enters in the block completation.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSLog(#"1. AZIENDE: BEGIN");
[Model syncAziende:^(id response, NSError *error) {
dispatch_semaphore_signal(semaphore);
NSLog(#"2. AZIENDE: FINISH");
}];
/*BLOCKS HERE */dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"3. AZIENDE: BEGIN");
[Model syncContatti:^(id response, NSError *error) {
NSLog(#"4. AZIENDE: FINISH");
}];
Here's the output:
2014-03-26 09:35:56.561 NSalesCDC[1071:60b] 1. AZIENDE: BEGIN
Trying to use semaphores is not the correct approach to this.
Instead, chain your callbacks together. You can create your blocks outside of each other to prevent horrible, pyramid-like callback hell.
This should work for you:
// The block that is called when syncContatti: is complete
void (^contattiBlock)(id, NSError *) = ^(id response, NSError *error) {
NSLog(#"4. AZIENDE: FINISH");
};
// The block that is called when syncAziende: is complete
void (^aziendeBlock)(id, NSError *) = ^(id response, NSError *error) {
NSLog(#"2. AZIENDE: FINISH");
// Now, we know that syncAziende: is finished, we can start the next step
[Model syncContatti:conCattiBlock];
};
// Now we begin the entire chain of events
NSLog(#"1. AZIENDE: BEGIN");
[Model syncAziende:aziendeBlock];
One downside of this is that you have to define your blocks in reverse-order, but that's not too bad.
You can use dispatch_barrier_async(). dispatch_barrier_async() will wait until all the tasks that are scheduled before the barrier to finish execution and then it will start execution. All the tasks scheduled after the barrier will wait for the barrier to finish.
dispatch_async(myQueue,
// this will start working now
});
dispatch_barrier_async(myQueue,
// this will wait until the previous block finish
//and will block the next one from execution
})
dispatch_async(myQueue,
// this will wait for the barrier to finish
});
Use it this way:
- (void) testSomethingAPI
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[Model syncAziende: ^(id response, NSError *error)
{
// Your Stuff here...
dispatch_semaphore_signal(semaphore);
}];
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 1.f]];
}
}
You may use NSOperation dependencies.
E.g.
NSOperationQueue * que = [[NSOperationQueue alloc] init];
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"first");
}];
NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"second");
}];
[op2 addDependency:op];
[que addOperations:#[op,op2] waitUntilFinished:NO];
You can also call the second block within the first or use other guys approaches
If your reply to my comment above really is the structure of your code, it cries out for refactoring. The repetition is a good candidate for abstraction.
Perhaps something like:
static const struct {
SEL selector;
NSString* message;
} steps[] = {
{ #selector(syncAziende:), #"Sincrinizzo i contatti" }.
{ #selector(syncContatti:), #"Sincrinizzo le destinazioni" }.
// ...
};
- (void) doStep:(int) step
{
if (step < sizeof(steps) / sizeof(steps[0]))
{
[Model performSelector:steps[step].selector withObject:[^(id response, NSError *error){
hud.labelText = [NSString stringWithFormat:#"%d/%d: %#", step + 1, sizeof(steps) / sizeof(steps[0]), steps[step].message];
[self doStep:step + 1];
} copy]];
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
hud.mode = MBProgressHUDModeText;
hud.labelText = #"Sincronizzazione terminata";
[hud hide:YES afterDelay:1.5];
});
}
}
...
[self doStep:0];
At first
I have this
ZTCAPIClient *api = [ZTCAPIClient sharedClient];
__block BOOL sessionSuccess = NO;
//Get session
[api getPath:#"api-getsessionid.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSMutableDictionary *dict = [self dealWithZTStrangeJSON:JSON];
if ([dict count]) {
NSLog(..something..);
sessionSuccess = YES;
NSLog(#"inside:%u",sessionSuccess);
} else {
NSLog(#"ERROR: Get no session!");
sessionSuccess = NO;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#",error);
sessionSuccess = NO;
}];
[api.operationQueue waitUntilAllOperationsAreFinished];
NSLog(#"outside:%u",sessionSuccess);
but I will get:
outside:0
inside:1
I know it's the async reason.
So I searched on the Internet, then I found this: wait until multiple operations executed - including completion block (AFNetworking)
So I try it:
ZTCAPIClient *api = [ZTCAPIClient sharedClient];
__block BOOL sessionSuccess = NO;
dispatch_group_t group = dispatch_group_create();
//Get session
dispatch_group_enter(group);
[api getPath:#"api-getsessionid.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSMutableDictionary *dict = [self dealWithZTStrangeJSON:JSON];
if ([dict count]) {
NSLog(..something..);
sessionSuccess = YES;
NSLog(#"inside:%u",sessionSuccess);
} else {
NSLog(#"ERROR: Get no session!");
sessionSuccess = NO;
}
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#",error);
sessionSuccess = NO;
dispatch_group_leave(group);
}];
//[api.operationQueue waitUntilAllOperationsAreFinished];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
DLog(#"outside:%u",sessionSuccess);
then I get nothing...
nothing output.
Where is wrong?
You're probably not getting any output because your program is never moving past the call to dispatch_group_wait. If it did, then you'd see the "outside" log statement.
If dispatch_group_wait is never returning, then there must still be something in the group. In your sample code, you add one thing to the group with dispatch_group_enter and then remove it in either the success or failure handler for the api call with dispatch_group_leave. This means that dispatch_group_leave is not being called for some reason.
My suspicion is that the reason the blocks are not being called is that they will be invoked asynchronously on the same dispatch queue that your outer code runs on. If this is the case, then they can't run until dispatch_group_wait returns and dispatch_group_wait cannot return until the blocks run. This is called deadlock. (Edit: Alternatively, it could be that some part of the program that invokes the success or failure blocks is the part that is leading to deadlock. Either way, the result is that the blocks can't get called since dispatch_group_wait never returns.)
The other possibility is that the method -dealWithZTStrangeJSON: never returns for some reason. If this is the case, then the success block will be invoked (you could set a breakpoint on its first line to verify), but it will never make it to dispatch_group_leave.
In either case, I would recommend that you think about solving your problem another way instead of waiting for the operation to finish. Perhaps you can do the things that you were planning to do after dispatch_group_wait returns inside of the success handler instead (or another way of thinking about it would be that the success or failure handler could call a method that does the things you're currently doing after dispatch_group_wait—either way will work, but sometimes I find that it's easier to keep my code organized by calling out to a method instead of putting all of the code in a block. This could be especially useful if you want to share some code between the success and failure blocks).