AFNetworking-2 waitUntilFinished does not work

I know that there is another similar question , but it is for an older version of AFNetworking and does not actually answer it.

I have the following code:

AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager]; manager.securityPolicy.allowInvalidCertificates = YES; manager.requestSerializer = [AFJSONRequestSerializer serializer]; [manager.requestSerializer setAuthorizationHeaderFieldWithUsername: currentUser() password: currentPassword()]; __block NSDictionary* response = nil; AFHTTPRequestOperation* operation = [manager GET: @"https://10.20.30.40:8765/foobar" parameters: [NSDictionary dictionary] success:^(AFHTTPRequestOperation* operation, id responseObject){ response = responseObject; NSLog(@"response (block): %@", response); } failure:^(AFHTTPRequestOperation* operation, NSError* error){ NSLog(@"Error: %@", error);} ]; [operation waitUntilFinished]; NSLog(@"response: %@", response); ... 

If I ran this, then I will see in my journal:

 2013-12-09 09:26:20.105 myValve[409:60b] response: (null) 2013-12-09 09:26:20.202 myValve[409:60b] response (block): { F00005 = ""; F00008 = ""; F00013 = ""; } 

NSLog , after the first start of waitUntilFinished . I expected him to fire a second. What am I missing?

+8
ios objective-c afnetworking-2
source share
1 answer

A few thoughts:

  • The problem is that waitUntilFinished will wait for the main network operation to complete, but it will not wait for success or failure blocks to complete. If you want to wait for the completion blocks, you can use the semaphore:

     __block NSDictionary* response = nil; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); AFHTTPRequestOperation* operation = [manager GET: @"https://10.20.30.40:8765/foobar" parameters: [NSDictionary dictionary] success:^(AFHTTPRequestOperation* operation, id responseObject){ response = responseObject; NSLog(@"response (block): %@", response); dispatch_semaphore_signal(semaphore); } failure:^(AFHTTPRequestOperation* operation, NSError* error){ NSLog(@"Error: %@", error); dispatch_semaphore_signal(semaphore); }]; NSLog(@"waiting"); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // [operation waitUntilFinished]; NSLog(@"response: %@", response); 

    You can also wrap this in your own parallel subclass of NSOperation by placing isFinished in the AFHTTPRequestOperation termination AFHTTPRequestOperation , excluding semaphore in the process.

    Note. Be sure to specify completionQueue if you make semaphores in the main queue because in the absence of this, AFNetworking by default sends completion handlers to the main queue, and you can block it.

  • As an aside, you should never block the main queue (bad UX, your application can be killed by the watchdog process, etc.), so if you do this from the main queue, I would be discouraged by using either waitUntilFinished , or semaphore. It’s better to just initiate everything you need from the completion blocks, allowing the main queue to continue executing while this asynchronous network operation is running, for example:

     [activityIndicatorView startAnimating]; AFHTTPRequestOperation* operation = [manager GET: @"https://10.20.30.40:8765/foobar" parameters: [NSDictionary dictionary] success:^(AFHTTPRequestOperation* operation, id responseObject){ // do whatever you want with `responseObject` here // now update the UI, eg: [activityIndicatorView stopAnimating]; [self.tableView reloadData]; } failure:^(AFHTTPRequestOperation* operation, NSError* error){ // put your error handling here // now update the UI, eg: [activityIndicatorView stopAnimating]; }]; // NSLog(@"waiting"); // dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // // [operation waitUntilFinished]; // NSLog(@"response: %@", response); 

It looks like you want your model to allow the user interface to make any necessary updates when the model object makes its updates. Thus, you can use your own block parameters so that the view controller can tell the model object what to do when it is done (instead of using waitUntilFinished or a semaphore to make the network control block the main queue). For example, suppose your model had this method:

 - (void)updateModelWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failure { AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager]; manager.securityPolicy.allowInvalidCertificates = YES; manager.requestSerializer = [AFJSONRequestSerializer serializer]; [manager.requestSerializer setAuthorizationHeaderFieldWithUsername: currentUser() password: currentPassword()]; AFHTTPRequestOperation* operation = [manager GET: @"https://10.20.30.40:8765/foobar" parameters: [NSDictionary dictionary] success:^(AFHTTPRequestOperation* operation, id responseObject){ // do your model update here // then call the success block passed to this method (if any), // for example to update the UI if (success) success(); } failure:^(AFHTTPRequestOperation* operation, NSError* error){ NSLog(@"Error: %@", error); // if caller provided a failure block, call that if (failure) failure(error); }]; } 

Then your view controller might do something like:

 [modelObject updateModelWithSuccess:^{ // specify UI updates to perform upon success, eg // stop activity indicator view, reload table, etc. } failure:^(NSError *error){ // specify any UI updates to perform upon failure }] 

On the bottom line, your code can use the same style of completion blocks that AFNetworking uses. If you want the model to pass information back, you can add additional parameters to the completion blocks yourself, but I believe that the above illustrates the main idea.

+32
source share

All Articles