Reputation: 1263
I have NSOperation with AFHTTPClient request. In end of operation i need to perform another N operations with requests and wait that requests will be finished to mark main operation as finished
@interface MyOperation : OBOperation
@end
@implementation MyOperation
- (id)init
{
if (self = [super init]) {
self.state = OBOperationReadyState;
}
return self;
}
- (void)start
{
self.state = OBOperationExecutingState;
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://google.com"]];
[client getPath:@"/"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
NSMutableArray *ops = [NSMutableArray array];
for (int i = 1; i < 10; i++) {
MyInnerOperation *innerOp = [[MyInnerOperation alloc] initWithNumber:@(i)];
[ops addObject:innerOp];
}
[queue addOperations:ops waitUntilFinished:YES];
self.state = OBOperationFinishedState;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
self.state = OBOperationFinishedState;
NSLog(@"error");
}];
}
@end
Link to OBOperation
source at end of question. It's a simple class that add useful methods to control NSOperation
flow
Sample of Inner Operation:
@interface MyInnerOperation : OBOperation
- (id)initWithNumber:(NSNumber *)number;
@end
@implementation MyInnerOperation
- (id)initWithNumber:(NSNumber *)number
{
if (self = [super init]) {
_number = number;
self.state = OBOperationReadyState;
}
return self;
}
- (void)start
{
self.state = OBOperationExecutingState;
NSLog(@"begin inner operation: %@", _number);
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://google.com"]];
[client getPath:@"/"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"inner operation success: %@", _number);
self.state = OBOperationFinishedState;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
self.state = OBOperationFinishedState;
NSLog(@"inner operation error: %@", _number);
}];
}
@end
So if i begin my operation:
MyOperation *op = [MyOperation new];
[_queue addOperation:op];
I see in console begin inner operation: 1
and that's all! My app totally freeze (even UI)
After some exploration i decide that freeze caused by [queue addOperations:ops waitUntilFinished:YES];
. If i don't wait for finish, my inner operations work as expected, but MyOperation finished before child operations will be completed.
So now i have workaround with dependent block operation:
NSBlockOperation *endOperation = [NSBlockOperation blockOperationWithBlock:^{
self.state = OBOperationFinishedState;
}];
NSMutableArray *ops = [NSMutableArray arrayWithObject:endOperation];
for (int i = 1; i < 10; i++) {
MyInnerOperation *innerOp = [[MyInnerOperation alloc] initWithNumber:@(i)];
[ops addObject:innerOp];
[endOperation addDependency:innerOp];
}
[queue addOperations:ops waitUntilFinished:NO];
But i still totally don't understand what's real problem of this freeze. Any explanation will be very useful.
OBOperaton class source: https://dl.dropboxusercontent.com/u/1999619/issue/OBOperation.h https://dl.dropboxusercontent.com/u/1999619/issue/OBOperation.m
Whole project: https://dl.dropboxusercontent.com/u/1999619/issue/OperationsTest.zip
Upvotes: 3
Views: 913
Reputation: 438232
The reason you're deadlocking is that AFNetworking dispatches the completion blocks to the main queue. Therefore, waitUntilFinished
in that first success
handler will block the main queue until the subordinate requests finish. But those subordinate requests cannot finish because they need to dispatch their completion blocks to the main queue, which the first operation is still blocking.
Clearly, you never want to block the main queue anyway, but you receive a deadlock if you block the main queue waiting for operations, which, themselves, need the main queue.
Upvotes: 4