NSOperationQueue: Trouble understanding the order [duplicate] - ios

This question already has answers here:
NSOperationQueue serial FIFO queue
(3 answers)
Closed 9 years ago.
I'm having trouble understanding the way NSOperationQueue works.
Say I have:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
[queue addOperationWithBlock:^{
[someObject someSelector];
}];
[queue addOperationWithBlock:^{
[someObject anotherSelector];
}];
The second block is being called even before the first block finishes - the opposite of what I want. I tried using – performSelectorOnMainThread:withObject:waitUntilDone: instead, but the second block is still being executed first - presumably because the block thread is not being completed on the main thread, and so it is not blocked with waitUntilDone. I added a break point inside my someSelector block, and it is reached after a break point inside the second block.
I don't quite get it. Help me!!

If there are explicit dependencies between the operations, then use addDependency:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
NSOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[someObject someSelector];
}];
NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[someObject anotherSelector];
}];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];
If your operations are doing asynchronous activity, then you should define a custom operation, and only call completeOperation (which will post the isFinished message) when the asynchronous task is done).
// SomeOperation.h
#import <Foundation/Foundation.h>
#interface SomeOperation : NSOperation
#end
and
// SomeOperation.m
#import "SomeOperation.h"
#interface SomeOperation ()
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#end
#implementation SomeOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
#pragma Configure basic operation
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)main
{
// start some asynchronous operation
// when it's done, call `completeOperation`
}
#pragma mark - Standard NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
#end
Thus, with the following code, it won't start operation2 until the asynchronous task initiated in main in SomeOperation object, operation1, calls its completeOperation method.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount=1;
NSOperation *operation1 = [[SomeOperation alloc] init];
NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[someObject anotherSelector];
}];
[operation2 addDependency:operation1];
[queue addOperation:operation1];
[queue addOperation:operation2];

Related

Hold NSOperationQueue until previous operation completes

I want to perform few operations and need to start the next operation only upon completion of the previous one. The operation I'm adding will send async call to the server and receive data. I want to start the next operation only after the first call to the server finish receiving data from the server. How to do that?
{....
PhotoDownloader *pd = [[PhotoDownloader alloc] init];
[GetGlobalOperationQueue addOperation:pd];
}
Inside the PhotoDownloader I will allocate the required parameters and call a Global function which handles all the request
[GlobalCommunicationUtil sendServerReq:reqObj withResponseHandler:self];
Inside the sendServerReq method I will construct the URL request and send it to the server and this call is a "sendAsynchronousRequest" call. The PhotoDownloader will have the CommunicationUtil's delegate methods.
There are two parts to this question:
You asked:
How do I make one operation not start until the previous operation finishes?
To do this, you could, theoretically, simply make a serial queue (which is fine if you want to make all operations wait until the prior one finishes). With an NSOperationQueue, you achieve that simply by setting maxConcurrentOperationCount to 1.
Or, a little more flexible, you could establish dependencies between operations where dependencies are needed, but otherwise enjoy concurrency. For example, if you wanted to make two network requests dependent upon the completion of a third, you could do something like:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4; // generally with network requests, you don't want to exceed 4 or 5 concurrent operations;
// it doesn't matter too much here, since there are only 3 operations, but don't
// try to run more than 4 or 5 network requests at the same time
NSOperation *operation1 = [[NetworkOperation alloc] initWithRequest:request1 completionHandler:^(NSData *data, NSError *error) {
[self doSomethingWithData:data fromRequest:request1 error:error];
}];
NSOperation *operation2 = [[NetworkOperation alloc] initWithRequest:request2 completionHandler:^(NSData *data, NSError *error) {
[self doSomethingWithData:data fromRequest:request2 error:error];
}];
NSOperation *operation3 = [[NetworkOperation alloc] initWithRequest:request3 completionHandler:^(NSData *data, NSError *error) {
[self doSomethingWithData:data fromRequest:request3 error:error];
}];
[operation2 addDependency:operation1]; // don't start operation2 or 3 until operation1 is done
[operation3 addDependency:operation1];
[queue addOperation:operation1]; // now add all three to the queue
[queue addOperation:operation2];
[queue addOperation:operation3];
You asked:
How do I ensure that an operation will not complete until the asynchronous network request it issued has finished as well?
Again, there are different approaches here. Sometimes you can avail yourself with semaphores to make asynchronous process synchronous. But, much better is to use a concurrent NSOperation subclass.
An "asynchronous" NSOperation is simply one that will not complete until it issues a isFinished notification (thereby allowing any asynchronous tasks it initiates to finish). And an NSOperation class specifies itself as an asynchronous operation simply by returning YES in its isAsynchronous implementation. Thus, an abstract class implementation of an asynchronous operation might look like:
// AsynchronousOperation.h
#import Foundation;
#interface AsynchronousOperation : NSOperation
/**
Complete the asynchronous operation.
If you create an asynchronous operation, you _must_ call this for all paths of execution
or else the operation will not terminate (and dependent operations and/or available
concurrent threads for the operation queue (`maxConcurrentOperationCount`) will be blocked.
*/
- (void)completeOperation;
#end
and
//
// AsynchronousOperation.m
//
#import "AsynchronousOperation.h"
#interface AsynchronousOperation ()
#property (getter = isFinished, readwrite) BOOL finished;
#property (getter = isExecuting, readwrite) BOOL executing;
#end
#implementation AsynchronousOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (instancetype)init {
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start {
if (self.isCancelled) {
if (!self.isFinished) self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation {
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
#pragma mark - NSOperation methods
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
#synchronized(self) { return _executing; }
}
- (BOOL)isFinished {
#synchronized(self) { return _finished; }
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:#"isExecuting"];
#synchronized(self) { _executing = executing; }
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:#"isFinished"];
#synchronized(self) { _finished = finished; }
[self didChangeValueForKey:#"isFinished"];
}
#end
Now that we have that abstract, asynchronous NSOperation subclass, we can use it in our concrete NetworkOperation class:
#import "AsynchronousOperation.h"
NS_ASSUME_NONNULL_BEGIN
typedef void(^NetworkOperationCompletionBlock)(NSData * _Nullable data, NSError * _Nullable error);
#interface NetworkOperation : AsynchronousOperation
#property (nullable, nonatomic, copy) NetworkOperationCompletionBlock networkOperationCompletionBlock;
#property (nonatomic, copy) NSURLRequest *request;
- (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(NetworkOperationCompletionBlock)completionHandler;
#end
NS_ASSUME_NONNULL_END
and
// NetworkOperation.m
#import "NetworkOperation.h"
#interface NetworkOperation ()
#property (nonatomic, weak) NSURLSessionTask *task;
#end
#implementation NetworkOperation
- (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(NetworkOperationCompletionBlock)completionHandler {
self = [self init];
if (self) {
self.request = request;
self.networkOperationCompletionBlock = completionHandler;
}
return self;
}
- (void)main {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *task = [session dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (self.networkOperationCompletionBlock) {
self.networkOperationCompletionBlock(data, error);
self.networkOperationCompletionBlock = nil;
}
[self completeOperation];
}];
[task resume];
self.task = task;
}
- (void)cancel {
[super cancel];
[self.task cancel];
}
#end
Now, in this example, I'm using block-based implementation of these asynchronous network requests, but the idea works equally well in delegate-based connections/sessions, too. (The only hassle is that NSURLSession specifies its task-related delegate methods to be part of the session, not the network task.)
Clearly the implementation of your own NetworkOperation class may differ wildly (use delegate patterns or completion block patterns, etc.), but hopefully this illustrates the idea of a concurrent operation. For more information, see the Operation Queues chapter of the Concurrency Programming Guide, notably the section titled "Configuring Operations for Concurrent Execution".
A swift version for an asynchronous operation (which was not very obvious):
final class NetworkOperation: Operation {
lazy var session: NSURLSession = {
return NSURLSession.sharedSession()
}()
private var _finished = false {
willSet {
willChangeValue(forKey: "isFinished")
}
didSet {
didChangeValue(forKey: "isFinished")
}
}
private var _executing = false {
willSet {
willChangeValue(forKey: "isExecuting")
}
didSet {
didChangeValue(forKey: "isExecuting")
}
}
override var isAsynchronous: Bool {
return true
}
override var isFinished: Bool {
return _finished
}
override var isExecuting: Bool {
return _executing
}
override func start() {
_executing = true
execute()
}
func execute() {
task = session.downloadTaskWithURL(NSURL(string: "yourURL")!) {
(url, response, error) in
if error == nil {
// Notify the response by means of a closure or what you prefer
// Remember to run in the main thread since NSURLSession runs its
// task on background by default
} else {
// Notify the failure by means of a closure or what you prefer
// Remember to run in the main thread since NSURLSession runs its
// task on background by default
}
// Remember to tell the operation queue that the execution has completed
self.finish()
}
}
func finish() {
//Async task complete and hence the operation is complete
_executing = false
_finished = true
}
}
To serialize the operations:
let operationQueue = OperationQueue()
let operation1 = NetworkOperation()
let operation2 = NetworkOperation()
operation2.addDependency(operation1)
operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)
Is it manadatory to use NSOperationQueue?
This behaviour is very easy to implement with serial queues
https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
Assuming you have a class to manage the operations, you would create a serial dispatch queue in your init method with
queue = dispatch_queue_create("com.example.MyQueue", NULL);
And you would have a method to enqueue request, something like this
- (void) enqueueRequest:(NSURL *)requestURL
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(queue, ^{ /* get data from requestURL */ }) });
}
This way, only one request is active at one time, even though each request will be executed in a separated background thread, and several requests will be enqueued until the active request finishes.

NSOperation + setCompletionBlock

I have few different questions about NSOperation and NSOperationQueue and I know guys that yours answers will help me;
I have to load a big amount of images and I have created my own loader based on NSOperation, NSOperationQueue and NSURLConnection (asynchronous loading);
Questions:
If I set maxConcurrentOperationCount (for example 3) for queue (NSOperationQueue), does it mean that only 3 operations performed in the same time even queue has 100 operations?
When I set property maxConcurrentOperationCount for queue sometimes "setCompletionBlock" doesn't work and count (operationCount) only increases; Why?
MyLoader:
- (id)init
{
self = [super init];
if (self) {
_loadingFiles = [NSMutableDictionary new];
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 3;
_downloadQueue.name = #"LOADER QUEUE";
}
return self;
}
- (void)loadFile:(NSString *)fileServerUrl handler:(GetFileDataHandler)handler {
if (fileServerUrl.length == 0) {
return;
}
if ([_loadingFiles objectForKey:fileServerUrl] == nil) {
[_loadingFiles setObject:fileServerUrl forKey:fileServerUrl];
__weak NSMutableDictionary *_loadingFiles_ = _loadingFiles;
MyLoadOperation *operation = [MyLoadOperation new];
[operation fileServerUrl:fileServerUrl handler:^(NSData *fileData) {
[_loadingFiles_ removeObjectForKey:fileServerUrl];
if (fileData != nil) {
handler(fileData);
}
}];
[operation setQueuePriority:NSOperationQueuePriorityLow];
[_downloadQueue addOperation:operation];
__weak NSOperationQueue *_downloadQueue_ = _downloadQueue;
[operation setCompletionBlock:^{
NSLog(#"completion block :%i", _downloadQueue_.operationCount);
}];
}
}
MyOperation:
#interface MyLoadOperation()
#property (nonatomic, assign, getter=isOperationStarted) BOOL operationStarted;
#property(nonatomic, strong)NSString *fileServerUrl;
#property(nonatomic, copy)void (^OnFinishLoading)(NSData *);
#end
#implementation MyLoadOperation
- (id)init
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}
- (void)fileServerUrl:(NSString *)fileServerUrl
handler:(void(^)(NSData *))handler {
#autoreleasepool {
self.fileServerUrl = fileServerUrl;
[self setOnFinishLoading:^(NSData *loadData) {
handler(loadData);
}];
[self setOnFailedLoading:^{
handler(nil);
}];
self.url = [[NSURL alloc] initWithString:self.fileServerUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:25];
[request setValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
_data = [[NSMutableData alloc] init];
}
}
- (void)main {
#autoreleasepool {
[self stop];
}
}
- (void)start {
[self setOperationStarted:YES];
[self willChangeValueForKey:#"isFinished"];
_finished = NO;
[self didChangeValueForKey:#"isFinished"];
if ([self isCancelled])
{
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
_executing = NO;
[self didChangeValueForKey:#"isFinished"];
}
else
{
[self willChangeValueForKey:#"isExecuting"];
_finished = NO;
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return _executing;
}
- (BOOL)isFinished {
return _finished;
}
- (void)cancel {
[self.connection cancel];
if ([self isExecuting])
{
[self stop];
}
[super cancel];
}
#pragma mark -NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self OnFinishLoading]) {
[self OnFinishLoading](_data);
}
if (![self isCancelled]) {
[self stop];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
;
if (![self isCancelled]) {
[self stop];
}
}
- (void)stop {
#try {
__weak MyLoadOperation *self_ = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self_ completeOperation];
});
}
#catch (NSException *exception) {
NSLog(#"Exception! %#", exception);
[self completeOperation];
}
}
- (void)completeOperation {
if (![self isOperationStarted]) return;
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
You must start the connection in the Operation's start method, and not in fileServerUrl:handler:.
I would remove this method altogether, and only provide an init method with all required parameters where you can completely setup the operation. Then, in method start start the connection.
Additionally, it's not clear why you override main.
Modifying the state variables _executing and _finished could be more concise and more clear (you don't need to set them initially, since the are already initialized to NO). Only set them in the "final" method completeOperation including KVO notifications.
You also do not need a #try/#catch in stop, since function dispatch_async() does not throw Objective-C exceptions.
Your cancel method is not thread safe, and there are also a few other issues. I would suggest the following changes:
#implementation MyOperation {
BOOL _executing;
BOOL _finished;
NSError* _error; // remember the error
id _result; // the "result" of the connection, unless failed
completion_block_t _completionHandler; //(your own completion handler)
id _self; // strong reference to self
}
// Use the "main thread" as the "synchronization queue"
- (void) start
{
// Ensure start will be called only *once*:
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isCancelled && !_finished && !_executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
_self = self; // keep a strong reference to self in order to make
// the operation "immortal for the duration of the task
// Setup connection:
...
[self.connection start];
}
});
}
- (void) cancel
{
dispatch_async(dispatch_get_main_queue, ^{
[super cancel];
[self.connection cancel];
if (!_finished && !_executing) {
// if the op has been cancelled before we started the connection
// ensure the op will be orderly terminated:
self.error = [[NSError alloc] initWithDomain:#"MyOperation"
code:-1000
userInfo:#{NSLocalizedDescriptionKey: #"cancelled"}];
[self completeOperation];
}
});
}
- (void)completeOperation
{
[self willChangeValueForKey:#"isExecuting"];
self.isExecuting = NO;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
completion_block_t completionHandler = _completionHandler;
_completionHandler = nil;
id result = self.result;
NSError* error = self.error;
_self = nil;
if (completionHandler) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
completionHandler(result, error);
});
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self onFinishLoading]) {
[self onFinishLoading](self.result);
}
[self completeOperation];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (self.error == nil) {
self.error = error;
}
[self completeOperation];
}
In answer to your questions:
Yes, a maxConcurrentOperationCount of three means that only three will run at a time. Doing network requests like this is perfect example of when you'd want to use maxConcurrentOperationCount, because failure to do so would result in too many network requests trying to run, most likely resulting in some of the connections failing when using a slower network connection.
The main issue here, though, is that you're calling your operation's fileServerUrl method (which is starting the connection) from MyLoader. You've disconnected the request from the operation's start (defeating the purpose of maxConcurrentCount of 3 and possibly confusing the state of the operation).
The start method should be initiating the connection (i.e. don't start the request until one of those three available concurrent operations is available). Furthermore, since you cannot pass the URL and the handler to the start method, you should move your logic that saves those values to a customized rendition of your init method.
There are other minor edits we might suggest to your operation (main not needed, operationStarted is a little redundant, simplify the _executing/_finished handling, etc.), but the starting of the connection in fileServerUrl rather than being initiated by the start method is the key issue.
Thus:
- (id)initWithServerUrl:(NSString *)fileServerUrl
handler:(void(^)(NSData *))handler
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
// do your saving of `fileServerURL` and `handler` here, e.g.
self.fileServerUrl = fileServerUrl;
self.OnFinishLoading:^(NSData *loadData) {
handler(loadData);
}];
[self setOnFailedLoading:^{
handler(nil);
}];
}
return self;
}
- (void)startRequest {
self.url = [[NSURL alloc] initWithString:self.fileServerUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:25];
[request setValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
_data = [[NSMutableData alloc] init];
}
- (void)start {
if ([self isCancelled])
{
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self setOperationStarted:YES]; // personally, I'd retire this and just reference your `executing` flag, but I'll keep it here for compatibility with the rest of your code
[self willChangeValueForKey:#"isExecuting"];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[self startRequest];
}
For the first question, the answer is yes, if set 3 as a max number of operations, only 3 can be running togheter.
The second is bit strange problem and I'm not totally sure that this answer will be correct. When you leave operations to an NSOperationQueue, you can't be sure on which thread they will be executed, this lead a huge problem with async connection. When you start an NSURLConnection as usual you receive the delegate callbacks without a problem, that is because the connection is running on a thread with a living run loop. If you start the connection on a secondary thread, callbacks will be called on that thread, but if you don't keep the run loop alive they will be never received.That's where probably my answer isn't correct, GCD should take care of living run loops, because GCD queues runs on living threads. But if not, the problem could be that operations are started on a different thread, the start method is called, but the callbacks are never called. Try to check if the thread is always the main thread.

NSOperationQueue and passing data

In my app, I have my main file that creates a new instance of a class and then uses NSOperationQueue to run the class functions in the background, like so:
NSOperationQueue backgroundQueue = [NSOperationQueue new];
MyClass mc = [MyClass alloc];
NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:mc selector:#selector(runEvents) object:nil];
[backgroundQueue addOperation:operation];
MyClass then does stuff in RunEvents, but I'm having difficulty passing data to the UI. I'm just trying to update a label on my storyboard, which I can do in my main class that calls MyClass, but how do I update it from MyClass?
The typical answer is to create your class as a NSOperation subclass and give it a custom completion block. If your goal is update the UI or some model object in the completion block, make sure to dispatch that block back to the main queue:
// CustomOperation.h
#import <Foundation/Foundation.h>
typedef void(^CustomOperationCompletion)(NSString *string);
#interface CustomOperation : NSOperation
#property (nonatomic, copy) CustomOperationCompletion customOperationCompletion;
- (id)initWithCustomCompletion:(CustomOperationCompletion)completion;
#end
and
// CustomOperation.m
#import "CustomOperation.h"
#implementation CustomOperation
- (id)initWithCustomCompletion:(CustomOperationCompletion)completion {
self = [super init];
if (self) {
self.customOperationCompletion = completion;
}
return self;
}
- (void)main {
NSLog(#"%s starting", __FUNCTION__);
sleep(5);
NSString *string = [[NSDate date] description];
if (self.customOperationCompletion) {
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
self.customOperationCompletion(string);
}];
}
NSLog(#"%s ending", __FUNCTION__);
}
#end
Then you can invoke it with something like:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
CustomOperation *operation = [[CustomOperation alloc] initWithCustomCompletion:^(NSString *string) {
// update the UI with the results of the operation; here I'm just going to log it
NSLog(#"all done, string=%#", string);
}];
[queue addOperation:operation];
Clearly, change your CustomOperationCompletion parameters to include whatever you want to return (I'm just passing a string back).

Keeping an NSOperation in a queue even after it completes

Typically once the main method of the an NSOperation is completed, the op is marked completed and it is removed from the queue. However, my op makes networking calls, and I want to handle retries. How do I keep an NSOperation in an NSOperationQueue until I explicitly say it's ok to remove it?
I can't find the original source for the work I did on my current project.
I have subclassed NSOperation and do this...
Add private properties in the .m...
#property (nonatomic) BOOL executing;
#property (nonatomic) BOOL finished;
#property (nonatomic) BOOL completed;
Init the operation...
- (id)init
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
_completed = NO;
}
return self;
}
Add the functions to return the properties...
- (BOOL)isExecuting { return self.executing; }
- (BOOL)isFinished { return self.finished; }
- (BOOL)isCompleted { return self.completed; }
- (BOOL)isConcurrent { return YES; }
In the "start" function (this is the bit that the operationQueue calls...
- (void)start
{
if ([self isCancelled]) {
[self willChangeValueForKey:#"isFinished"];
self.finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:#"isExecuting"];
self.executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[NSThread detachNewThreadSelector:#selector(main) toTarget:self withObject:nil];
}
Then in the main put your working code...
- (void)main
{
#try {
//this is where your loop would go with your counter and stuff
//when you want the operationQueue to be notified that the work
//is done just call...
[self completeOperation];
}
#catch (NSException *exception) {
NSLog(#"Exception! %#", exception);
[self completeOperation];
}
}
Write the code for completeOperation...
- (void)completeOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
That's it.
As long as you have these then the operation will work.
You can add as many other functions and properties as you wish.
In fact, I have actually subclassed this class as I have a function that does all the work for different types of object (it's an upload thing). I have defined a function...
- (void)uploadData
{
//subclass this method.
}
Then all I have in the subclasses is a custom "uploadData" method.
I find this really useful as it gives you fine grain control on when to finish the operation etc...

Handling data returned from multiple NSOperation objects in an NSOperation object that depends on them

I am writing a web-connected application that needs to execute several asynchronous requests to load data needed lower down in the dependency tree.
Fig 1.
For visualization purposes, consider an example with ASIHTTPRequests A,B,C,D,E, and F:
A's url depends on the result of B and C,
and B's url depends on the result of D, E, and F.
B and C can be computed concurrently, and so can D, E, and F.
NSOperationQueue = [(D,E,F),(B,C),A]
Thus far, I have created an NSOperationQueue that contains a dependency tree of ASIHTTPRequests. However, the URLs of the ASIHTTPRequests should depend on the results of the previous operations, and, right now, they do not.
The question: What is the best way to pass the results of the computations performed by multiple NSOperations to the NSOperation that depends on them, and how can I set this up with ASIHTTPRequests?
Thanks in advance,
Julian Ceipek
I would do the following.
To start with, queue:
D, E, F and C
In the requestFinished delegate callback for D, E & F, check if the other all 3 requests have finished, if so send B.
Do the same for the callbacks for B & C - if they've both finished, send A.
You'd need some kind of object that's shared by all requests to store the results / status of earlier requests into.
I ended up solving this problem by wrapping the ASIHTTPRequest in a custom NSOperation object that populated the request in such a way that custom request B contained a pointer to an object in D, E, and F's ASIHTTPRequest UserInfo Dictionary. While I liked #JosephH's solution, I couldn't figure out how to easily generate a dictionary or array with dependency tree intact.
A simplified version of my custom NSOperationObject is provided below; any suggestions are welcome. I used Apple's Concurrency Programming Guide extensively as a reference, but as I have not had any prior experience extending the NSOperation class, I am sure that there is a better way to do this.
#import <Foundation/Foundation.h>
#import "SyncableData.h"
#import "ASIHTTPRequest.h"
#interface PushContentRequest : NSOperation <ASIHTTPRequestDelegate> {
BOOL executing;
BOOL finished;
id <SyncableData> data;
ASIHTTPRequest *request;
NSURL *url;
id <ASIHTTPRequestDelegate> delegate;
}
- (id)initWithDataObject:(id <SyncableData>)theData url:(NSURL *)theUrl delegate:(id <ASIHTTPRequestDelegate>)theDelegate;
#end
#import "PushContentRequest.h"
#implementation PushContentRequest
- (id)initWithDataObject:(id <SyncableData>)theData url:(NSURL *)theUrl delegate:(id <ASIHTTPRequestDelegate>)theDelegate {
if ((self = [super init])) {
executing = NO;
finished = NO;
data = [theData retain];
url = [theUrl retain];
delegate = [theDelegate retain];
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (void)start {
if ([self isCancelled]) {
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
request = [ASIHTTPRequest requestWithURL:url];
NSString *xmlToPost = [[NSString alloc] initWithString: [theData getXMLRep]];
[request appendPostData:[xmlToPost dataUsingEncoding:NSUTF8StringEncoding]];
[request setDelegate:self];
NSDictionary *userInfoDict = [[NSDictionary alloc] initWithObjectsAndKeys:data, #"theData", nil];
[request setUserInfo:userInfoDict];
[userInfoDict release];
[xmlToPost release];
[self willChangeValueForKey:#"isExecuting"];
[request start];
executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[pool release];
}
- (void)dealloc {
[delegate release];
[url release];
[data release];
[super dealloc];
}
- (void)requestFinished:(ASIHTTPRequest *)therequest {
[delegate requestFinished:therequest];
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (void)requestFailed:(ASIHTTPRequest *)therequest {
[delegate requestFailed:therequest];
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
#end
The delegate of this PushContentRequest currently handles the interpretation of the ASIHTTPRequest's UserInfo Dictionary and the request in my implementation, though I suppose that it might make more sense to do this processing within the PushContentRequest's body.

Resources