Implementation of a concurrent NSOperation
to abstract and help the creation of asynchronous operations.
We code in an asynchronous world. We promise.
-- PromiseKit
Every iOS developer certainly agrees with this statement, a great part of our code is highly asynchronous and this brings a few challenges to us. Luckily, we have a few mechanisms to structure our business logic, NSOperation
in combination with NSOperationQueue
is one of them, it's powerful and flexible.
However, despite all the flexibility, if you need to make an asynchronous call on a NSOperation
you're doomed. Remember that a NSOperation
will finish right after the -main
method returns. So, you will probably end up using a semaphore or something similar to lock the operation while the async call is running otherwise it will finish before time. This doesn't feel right.
Actually, you're not completely doomed, due the great flexibility offered by NSOperation
you're able to create a concurrent operation where you can control exactly when the operation should be considered finished and with this finish the operation only after the async call have completed.
Implement a concurrent operation it's not hard but there're a few things that you will need to implement in every operation of this kind. This is the main reason behind DRAsyncOperation
, a class that implements the base functionality required to use concurrent operations and, consequently, use asynchronous code in NSOperation
s.
pod 'DRAsyncOperations'
github "dmcrodrigues/DRAsyncOperations"
Drag all files located in DRAsyncOperation
folder to your project and you're done. You can also import the Xcode project into your workspace as a dependency.
Implementing an asynchronous operation using DRAsyncOperation
is straightforward and it may seem very similar to implementing a custom NSOperation
.
- Subclass
DRAsyncOperation
; - Override the method
-asyncTask;
where you implement your asynchronous code, you can think of it as the equivalent of-main
method from non-concurrent operations; - When your asynchronous code finishes, call the method
-finish
to finish the operation, this part is new comparing toNSOperation
.
The methods referred above are available in
DRAsyncOperationSubclass.h
.
You can also create an asynchronous operation using a simple block using DRAsyncBlockOperation
which may seem very similar to a NSBlockOperation
.
If you create an instance of DRAsyncOperation
and then call -start
to manually start the operation you should be aware that this call may not block until completion due the asynchronous nature of this operations. If you want the same behavior of a non-concurrent operation you should invoke -waitUntilFinished
after -start
.
// DRNetworkAsyncOperation.h
#import "DRAsyncOperation.h"
@interface DRNetworkAsyncOperation : DRAsyncOperation
@end
// DRNetworkAsyncOperation.m
#import "DRNetworkAsyncOperation.h"
#import "DRAsyncOperationSubclass.h"
@implementation DRNetworkAsyncOperation
- (void)asyncTask
{
NSURL *githubURL = [NSURL URLWithString:@"https://github.com"];
[[NSURLSession sharedSession] dataTaskWithURL:githubURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// Do your stuff ...
// When you're done, finish the operation
[self finish];
}];
}
@end
NSOperationQueue *queue;
CLGeocoder *geocoder;
CLLocation *location;
NSOperation *asyncOperation = [DRAsyncBlockOperation asyncBlockOperationWithBlock:^(DRAsyncBlockOperationFinishBlock finish) {
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
// Do your stuff ...
// When you're done, finish the operation
finish();
}];
}];
[queue addOperation:asyncOperation];
Swift is supported out-of-the-box, you only need to import the relevant headers in your Bridging Header.
// If you want to implement async tasks in operations
#import "DRAsyncOperation.h"
#import "DRAsyncOperationSubclass.h"
// If you want to implement async tasks in blocks
#import "DRAsyncBlockOperation.h"
The block API in Swift it's more compact in comparison with Objective-C.
var queue: NSOperationQueue
var geocoder: CLGeocoder
var location: CLLocation
var asyncOperation: DRAsyncBlockOperation!
asyncOperation = DRAsyncBlockOperation { (finish) -> Void in
// This is automatically checked for you at the beginning of the operation but you could check it during your execution
if asyncOperation.isCancelled() {
finish()
return
}
geocoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Do your stuff ...
// When you're done, finish the operation
finish()
})
}
queue.addOperation(asyncOperation)
DRAsyncOperation is released under the MIT License.