Composite NSOperation. Is this a bad idea? - objective-c

For an iOS4.X application I am working on, we often need to perform an HTTP request, then parse the results, and do something with the results, and so on.
For this I created an NSOperation class to allow for composition of NSOperations using an NSOperation queue. Is there any issue with using NSOperationQueues for small things like this. Some have told me that the queues should be a more permanent thing.
I don't expect the nesting to be more than 2 levels deep in our application.
Here's an example of such usage:
#implementation CompositeOperation
- (id)initWithOperations:(NSArray *)operations {
if ((self = [super init])) {
operations_ = [operations retain];
[[operations_ lastObject] addObserver:self forKeyPath:#"isFinished" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
-(void)dealloc {
[operations_ release];
[operationQueue_ release];
[super dealloc];
}
- (BOOL)isConcurrent {
return YES;
}
#synthesize isExecuting = isExecuting_;
#synthesize isFinished = isFinished_;
#synthesize operations = operations_;
- (void) start {
if (![self isCancelled]) {
operationQueue_ = [[NSOperationQueue alloc] init];
// TODO: Add code to execute this serially
[operationQueue_ addOperations:operations_ waitUntilFinished:NO];
}
}
- (void)cancel {
if (operationQueue_) {
[operationQueue_ cancelAllOperations];
}
[super cancel];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"isFinished"] && object == [operations_ lastObject]) {
[self setIsFinished:YES];
}
}
#end
Thanks,
Mike

I am the one who thinks it is a very good idea so that I even created library after it: CompositeOperations.
There are two operations: simple operation represented by COSimpleOperation object and composite operation represented by COCompositeOperation object.
Simple operation is a smallest possible unit - to quote documentation:
In a nutshell COSimpleOperation is a NSOperation with a small bit of convenience sugar on top of it. As an operational unit for composite operations it usually corresponds to one networking request or some small focused piece of work.
Composite operation is an operation which consists of sub-operations. To quote #mikelikespie:
The point of this object is to make it so one can represent multiple operations that are logically grouped as one operation.
...which is pretty much another description of Composite Design Pattern from Gang of Four Design Patterns.
Composite operation can be parallel or sequential.
Parallel operations are created just as in the code example in question:
NSArray *operations = #[
operation1, operation2, operation3
]; // each operation is NSOperation <COOperation> *
COCompositeOperation *parallelOperation = [[COCompositeOperation alloc] initWithOperations:operations];
To create sequential operation one should instantiate COCompositeOperation with an object conforming to COSequence protocol:
Sequential composition implies sequential flow: sub-operations are executed serially one after another. Sequencing is achieved via collaboration between COCompositeOperation and arbitrary class conforming to COSequence protocol which is used by composite operation as a delegate who decides what operations are and in which order to run them.
To make this composition of operations possible I needed to put small restriction on operations library works with: aside from being NSOperations both COSimpleOperation and COCompositeOperation also conform to <COOperation> protocol:
This conformance basically means that both operations when finished have 3 possible states:
a non-empty result field indicates success
a non-empty error field indicates failure
both empty result and error fields indicate that operation was cancelled from outside (using -[NSOperation cancel] method).
Operation can never have both result and error fields non-empty!
This convention allows Composite Operations to decide at a certain point whether to continue execution of particular group of operations or to stop it. For operations without a specific result [NSNull null] should be passed as result.
For me the rational behind this library was "just" to be able to represent operations so "that they are logically grouped as one operation". There are libraries that achieve the same kind of a higher-level functionality but at the same time they introduce concepts like: Signals in ReactiveCocoa or Promises like in PromiseKit which I don't really need or I would say do not agree with. I wanted something as simple as possible and based on good old well-known NSOperation/NSOperationQueue infrastructure so that's the point of the whole effort.
P.S. I hope this kind of answer fits the SO at least it corresponds exactly to what #mikelikespie was asking about 4 years ago.

Related

#synchronized(self) block at multiple places for multiple resources in multithreaded environment , objective c

Does locking in one function from a thread, blocks all other thread trying to acquire the lock in different functions.
We can use gcd for accessing the critical sections mentioned below, just wanted to know how #synchronized(self) works.
For ex.
Do multiple threads with ONLY writeString() calls gets blocked when the execution is in #synchronized(self){ } in writeString()?
OR
all the threads calling the functions of the same class with #synchronized(self){} (e.g.readDataFromFile()) gets blocked when execution is in #synchronized(self){ } in writeString() ?
-(void)writeString:(NSString*)string forObj:(id)obj
{
#synchronized(self)
{
[obj write:string];
}
}
-(void)readDataFromFile:(NSString*)file
{
#synchronized(self)
{
[self read];
}
}
#synchronized(A) can be thought of as a lock where A identifies the lock to use.
If you pass the same value for A into two calls to #synchronized(), then they'll use the same lock and be exclusive. If you pass two different values for two calls, then they will not be exclusive.
So for this one, we need to zoom out to a bit of a larger context.
In the case where you have two instances:
YourClass *a = [[YourClass alloc] init];
YourClass *b = [[YourClass alloc] init];
a will sync access to both methods for itself.
b will sync access to both methods for itself.
So for instance, concurrently from two different threads, a and b can both run -writeString:forObj: at the same time without blocking.
Now if you changed the implementation to use #synchronized(self.class) that entirely changes the game. Rather than syncing on the 'instance', you would be syncing on the 'class' as a whole, so every instance would have to sync access to the methods.
So for instance using the use #synchronized(self.class) implementation, concurrently from two different threads, if a and b both run -writeString:forObj: at the same time, that would then serialize those calls so only one runs while the other is blocked waiting for the sync to unlock.
I hope that clarifies the difference between locking on an instance of a class vs locking every instance for a given class.
Edit:
May also be worth noting, if you use #synchronized(self) in a 'class' method like the following:
// implementation of 'ExampleClass'
+(void)serializeWriting:(NSString*)string toObj:(id)obj {
#synchronized(self) {
[obj write:string];
}
}
That also changes the game, so anytime that method is called, its synced against the class itself, so something like [ExampleClass serializeWriting:#"some string" toObj:somethingElse] would only ever run the critical section [obj write:string] on a single thread no matter how many places/threads it was called from.

How to set value without trigger KVO

I use the following code to add KVO on object.
[self.model addObserver:self
forKeyPath:#"userName"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
Now I want to set the userName like below. Yes, it will trigger KVO.
self.model.userName = #"testUser";
However, I want to set value without trigger KVO. How to do that? Are there any method like below allowing me to do that?
[self.model setValue:#"testUser" forKey:#"userName" isSilent:YES];
Your design is broken if you want to do this. The point of key-value observing is that someone wants to know when a field changes so they register for notifications. The point of being key-value observing compliant is that you're keeping your options open as to how the rest of the system interacts with you.
What it sounds like you're doing is trying to hack around some problem where you don't want someone to know the true value of a property for some reason. So they think they're getting updates but actually if they were to check the property then it'd turn out you were deliberately lying.
As you'd expect, Cocoa doesn't have any mechanisms to support such hacks. They're extremely bad practice, breaking the whole structure of object-oriented programming.
Lecturing aside, you could write a custom setter that went directly to the instance variable. So, e.g.
- (void)setUserNameAndLieAboutItAsAShortSightedHack:(NSString *)newName
{
_userName = newName;
}
At the system level, key-value observing is implemented by creating a new version of the property setter that contains a call to the real setter and makes appropriate observer calls around the outside. So avoiding the real setter would avoid the notifications.
Core Data implements setPrimitiveValue:forKey: to allow you to do this. You can implement the same method in your object.
[self.model setPrimitiveValue:#"testUser" forKey:#"userName"];
When doing this however, it should be in the context of aggregating notifications where the observer is eventually notified with manual willChangeValueForKey: and didChangeValueForKey:.
You can use an ignore flag. Same idea as in the docs for User-Driven Updates.
// update the config object with the value the user chose.
- (IBAction)valueChanged:(UISlider *)slider{
self.ignore = YES;
self.config.sliderValue = slider.value;
self.ignore = NO;
}
// update the slider with the value from the config object.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == kSliderKVOContext) {
if(!self.ignore){
self.slider.value = [change[NSKeyValueChangeNewKey] doubleValue];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

How can I invoke a method as soon as a bool flag changes?

I want to perform [document doSomething] as soon as [document isBusy] is false. What is the best way to do it? I tried it with a while-loop but that delays the following code, which is not what I want.
If the action a semantic of your document model
From your question both isBusy and doSomething are features of your document and the action to doSomething when isBusy goes false is appears to be a semantic of your document model and should therefore be implemented by your document model, i.e. something like:
- (void) setIsBusy:(BOOL)flag
{
if(flag != _isBusy) // check if this is a change
{
_isBusy = flag;
if(flag)
{
// doSomething, or schedule doSomething if it is a long operation etc., e.g.
[self doSomething];
}
}
}
Using KVO to implement semantics within a single object is probably unusual, but there are cases where it is useful. In this case it would replace a direct action with an indirect one - KVO would execute doSomething at exactly the same point as the above sample code, there would just be a number of intermediate system methods between setIsBusy and doSomething plus the associated overhead of setting up the KVO.
If the action is a semantic of your document's client
Of course, if the linkage between these two is independent of your document model, i.e. is a semantic of the client of your document, then KVO is appropriate and would be implemented in your client. Your client would register as an observer of your document, i.e. something like:
[document addObserver:self
forKeyPath:#"isBusy"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
in a method of the client. Then in your client when a notification of the change is received take the appropriate action:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:#"isBusy"] && object == document)
[document doSomething;
else
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
If isBusy has a associated setter method, you can call doSomething in that setter. A better option would be to use Key Value Observing to observe the value of isBusy and take action when it changes.
Use KVC/KVO (Key Value Coding and Key Value Observation). The subject is covered well in the Apple Documentation. KVO and KVC is a basic programming technique which provides may solutions to your problem.

How do I observe the "operations" property of NSOperationQueue?

I want to be notified whenever a NSOperation has been added or removed from my NSOperationQueue. I'm trying to set up key-value observing for the "operations" property (an array of NSOperations currently in the Queue) but its not being fired. Is there something wrong with my syntax?
#implementation myOperationQueueSubclass
-(id)init
{
if (self = [super init])
{
// Initialization code here
[self addObserver:self
forKeyPath:#"operations"
options:0
context:nil];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(#"queue changed...");
if ([keyPath isEqualToString:#"operations"]) {
if (self.operationCount == 0) {
// No ops
} else {
// Has ops
}
}
}
There's nothing wrong with your syntax, but you're observing the array property, not the array itself (which doesn't issue KVO notifications when it's mutated anyways).
You'll get notified if it's reassigned for some reason, but NSOperationQueue would have to take the trouble to make the notifications manually, or use the KVC accessors, to allow others to see when objects are added to or removed from its operations.
I had a similar need and created a very thin operations manager, OperationsRunner in this github project. Other objects interface with this class instead of the NSOperationsQueue directly. It has only a handful of methods - run an operation, cancel it, ask for the number of operations in the queue, etc.
What I did was to use a mutable set to hold a reference to an operation that was added to the operations queue, and remove it when the operation completed or cancelled - sort of a shadow container.
The nice this about this class is that you can easily add it to any kind of other class to manage operations, and quickly cancel all pending operations.

Memory management related to void* context in Cocoa

There are a number of Foundation classes that allow you to provide a pointer to void that is passed as an argument to a callback function at a later time. For instance, addObserver:forKeyPath:options:context: of NSKeyValueObserving.
Since a pointer to void may not extend NSObject, functions which accept such an argument cannot be expected to retain it. Therefore, your code must look similar to the following:
- (void)sharedInit
{
MyObject *myObject = [[MyObject alloc] init];
[x addObserver:y forKeyPath:#"z" options:0 context:myObject];
// cannot (auto)release myObject here
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
MyObject *myObject = (MyObject *)context;
[myObject release]; // myObject is released here
}
This makes sense, although it seems to violate every principle of Cocoa/Objective-C object ownership. In addition, if you forget to manually manage memory in such a case it often (although not always) results in a EXC_BAD_ACCESS crash. Furthermore, the Xcode analyzer complains.
The reason I am asking this question is because I am writing a network connection library that makes use of the same sort of context pointers in its public API. However, after having had to track down a number of memory management bugs in my own code related to the resulting need for manual memory management, I believe there must be a better way. One solution is to change the types of the context arguments of my API to (id<NSObject>) rather than (void*).
Why do Foundation classes often use (void*) rather than (id<NSObject>)?
MHC is correct about the reasoning. There is no requirement that the object be an NSObject. But you are doing your memory management incorrectly here in any case, as you note.
Rather than leaking an object, and then trying to clean it up later, the common pattern for managing an object in this case is to store it as an ivar of the registering class. Another common pattern is to pass self as the context, making sure to unregister for callbacks during -dealloc.
Because it is not always id. Data could be a C structure, or a Core Foundation object, or even a scalar.