问题
Suppose that I use another SDK (which I do not have control of) with an API that imports 1 file asynchronously, and calls a completion callback on completion. The following is an example API.
func importFile(filePath: String, completion: () -> Void)
I need to import 10 files (one by one) using this API, but I need it to be cancellable, e.g. after Files 1,2,3 has been successfully imported, while File 4 is being imported, I want to be able to cancel the whole set of operations (import of the 10 Files), such that File 4 will finish (since it already started), but Files 5-10 will not be imported anymore.
In addition, I also need to report progress of the import. When File 1 has been imported successfully, then I should report progress of 10% (1 out of 10 has been finished).
How can I achieve this?
I am considering using NSOperationQueue with 10 NSOperations, but it seems that progress reporting will be difficult.
回答1:
So, I believe that this is the following you want from your question:
- Import n files one by one in a queue
- Report progress when each file is imported
- Ability to cancel operation in the middle
It can be achieved using NSOperationQueue
and NSBlockOperation
in the following way.
- Create
AsyncBlockOperation
andNSOperationQueue+AsyncBlockOperation
classes from code given in answer for the following StackOverflow question: NSOperation wait until asynchronous block executes - Import both the classes into the file they are going to be used
Create an operation queue
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; operationQueue.maxConcurrentOperationCount = 1; operationQueue.name = @"com.yourOrganization.yourProject.yourQueue";
Create a function which gives you a callback for getting progress
- (void)importFilesFromFilePathArray:(NSArray *)pathsArray inOperationQueue:(NSOperationQueue *)operationQueue withProgress:(void (^)(CGFloat progress))progressBlock { }
Inside the function defined in
2
, useNSBlockOperation
for performing your operations in theNSOperationQueue
for (int i = 0; i < pathsArray.count; i++) { [operationQueue addAsyncOperationWithBlock:^(dispatch_block_t completionHandler) { [self importFile:(NSString *)[pathsArray objectAtIndex:i] completion:^(bool completion) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ CGFloat progress = (100 * (float)(i+1)/pathsArray.count); progressBlock(progress); if (progress == 100) { successBlock(YES); } }]; completionHandler(); }]; }]; }
For cancelling operations, we can simply use the
operationQueue
that we created in the 1st step[operationQueue cancelAllOperations];
I tried this code myself. It's working well. Feel free to suggest improvements to make it better :)
回答2:
I think you should add dependencies on your operation. The idea is this:
1Op = NSOperation..
2Op = NSOperation..
.
.
10Op = NSOperation..
10Op.addDependency(9Op)
9Op.addDependency(8Op) and so on...
Then your operation subclass should override the cancel method in this way
cancel() {
super.cancel()
for dep in dependencies {
dep.cancel()
}
}
回答3:
NSOperationQueue
offers a nice object oriented abstaraction and is the way I would go.
- Create a
NSOperationQueue
namedimportQueue
For each import:
- Create a
NSOperation
- Store the operation to reach and possibly cancle it later on, for instance in a dictionary [ImportNumber:NSOperation]
- Add the operation to
importQueue
In regards to the progress state:
Option 1:
NSOperationQueue
has a property called operations
, whichs count you are able to observe. (importQueue.operations.count
).
Option 2:
NSOperation
offers completion blocks. You can increase a counter when queueing the operation and decrease it within the completion block or when cancelling.
Further reading: Apple documentation, asynchronous-nsoperation-why-and-how, nice but oldish tutorial.
来源:https://stackoverflow.com/questions/40265055/cancellable-set-of-asynchronous-operations-with-progress-reporting-in-ios