Granularity Status NSBlockOperation

I extended NSOperationQueue to add NSBlockOperation with a specific NSString as an identifier.

The identifier value is stored in NSMutableArray , which serves as the registry. This is how I implement the registry.

 -(void)addOperation:(NSOperation *)operation withID:(NSString*)operationID { @synchronized(self.queueReference) { [self.queueReference addObject:operationID]; // <-- just a mutable array } [operation setCompletionBlock:^(){ @synchronized(self.queueReference) { [self.queueReference removeObject:operationID]; } }]; [self addOperation:operation]; } 

I basically add a termination block that cleans the registry when this particular operation is completed.

However, although this works, I need to add additional detail to the queue.

I use only the queue with the operation of the block, and during the execution of the block I can send a different NSNotification listener depending on how it happened.

What I tried to achieve:

The caller tries to add an NSBlockOperation with identifier to the queue. If there is already such an identifier in the queue, just do not add a block, and the calling class sets itself up as a listener.

What is missing? It is not enough to check the identifier, it may be the case when NSBlockOperation has already sent NSNotification , but the completion block has not yet been called.

Thus, the caller class requests a queue that says that the identifier exists in the registry, and the caller mistakenly set himself to listen to a notification that will never arrive, since it has already been sent.

Instead, there will be a scenario: the caller will request a queue that says that "the identifier is in the registry", but NSNotification sent. And the caller puts the NSBlockOperation in the queue.

Checking the registry using a simple method:

 -(BOOL)hasOperationWithID:(NSString*)operationID { @synchronized(self.queueReference) { return [self.queueReference containsObject:operationID]; } } 

but at the moment I donโ€™t have a big idea on how to extend such a method. The code I'm working on is kind of "academic", it doesn't serve any specific purpose, I'm just trying to experiment. Therefore, I have great flexibility in the code. But this is a completely new subject for me, so please be as specific as possible, taking into account the shortcomings of the proposed implementation.

+4
source share
1 answer

It looks like your current system has three main events:

  • The operation is added to the queue.
  • The operation sends a notification when executed
  • The operation completion block is called

If the queue itself does not explicitly listen to any NSNotifications that can be sent in blocks, it does not know if they have occurred yet. But even if he listens, the ordering in which NSNotifications observers are NSNotifications is non-deterministic. In other words, even if the queue listens for the notification and blocks its callback using enqueue / dequeue operations, it could (and ultimately) be NSNotification late for another client to listen on this NSNotification , and you erroneously rejected the operation.

Consider this alternative: instead of using the completion block to manage the list of identifiers, use the notification itself - have a queue descriptor that sends notifications. In other words, letโ€™s get rid of the third event, and sending notifications does a double duty to maintain the list of identifiers. The easiest way to do this looked like this:

Title:

 // // SONotifyingOperationQueue.h // NotifyingOpQueue // typedef void (^SOSendNotificationBlock)(NSDictionary* userInfo); typedef void (^SONotifyingBlock)(SOSendNotificationBlock sendNotificationBlock); @interface SONotifyingOperationQueue : NSOperationQueue - (BOOL)addOperationForBlock:(SONotifyingBlock)block withNotificationName:(NSString*)notificationName; @end 

Implementation

 // // SONotifyingOperationQueue.m // NotifyingOpQueue // #import "SONotifyingOperationQueue.h" @implementation SONotifyingOperationQueue { NSMutableSet* _names; } - (BOOL)addOperationForBlock: (SONotifyingBlock)block withNotificationName: (NSString*)notificationName { notificationName = [[notificationName copy] autorelease]; BOOL shouldAdd = NO; @synchronized(self) { _names = _names ? : [[NSMutableSet alloc] init]; if (![_names containsObject: notificationName]) { [_names addObject: notificationName]; shouldAdd = YES; } } if (shouldAdd) { NSBlockOperation* blockOp = [[[NSBlockOperation alloc] init] autorelease]; __block SONotifyingOperationQueue* blockSelf = self; SOSendNotificationBlock notificationBlock = ^(NSDictionary* userInfo){ @synchronized(blockSelf) { [blockSelf->_names removeObject: notificationName]; // Sending the notification from inside the @synchronized makes it atomic // with respect to enqueue operations, meaning there can never be a missed // notification that could have been received. [[NSNotificationCenter defaultCenter] postNotificationName: notificationName object: blockSelf userInfo: userInfo]; } }; dispatch_block_t executionBlock = ^{ block(notificationBlock); }; [blockOp addExecutionBlock: executionBlock]; [self addOperation: blockOp]; } return shouldAdd; } - (void)dealloc { [_names release]; [super dealloc]; } @end 

This approach makes several changes to your initial approach. First, here the API adds blocks, not NSOperations . You can do the same with the NSOperation subclass, but this will be more code and will not change the general template. It also combines the concept of an identifier and a notification name. If the operation can send several different NSNotifications , this will not work without changes, but again, the general template will be the same. An important feature of this template is that your id / name check is now blocked by sending notifications itself, which gives a strong guarantee that if someone goes to add a new block / operation to the queue and another operation with the same identifier / name has not yet released it notification, a new operation will not be added, but if the notification has been dismissed, it will be added even if the previous block has not yet been completed.

If the NSOperation object was somehow important here, you could also return here the method that it creates for the supplied block.

NTN.

0
source

All Articles