3

I was working through an example in the concurrency chapter of "More iPhone 3 Development," and can't get KVO on an NSOperationQueue working as expected. I create an NSOperationQueue and observe its operations array using:

NSOperationQueue *newQueue = [[NSOperationQueue alloc] init];
self.queue = newQueue;
[newQueue release];
[queue addObserver:self
        forKeyPath:@"operations"
           options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
           context:NULL];

When the first NSOperation is added to the queue, I expect it to be added to its underlying operations array (which the iOS documentation says is KVO-compliant) and hence, in the change dictionary, to find a mapping from NSKeyValueChangeKindKey to NSKeyValueChangeInsertion, along with a mapping from NSKeyValueChangeNewKey to the added NSOperation. But I wasn't seeing any kind of value NSKeyValueChangeInsertion.

I know the debugger is pro and all, but in the interest of having something useful to copy here, I started my observer method with:

- (void) observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object
                         change:(NSDictionary *)change
                        context:(void *)context {
  NSNumber *kind = [change objectForKey:NSKeyValueChangeKindKey];
  NSObject *newValue = [change objectForKey:NSKeyValueChangeNewKey];
  NSObject *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
  NSIndexSet *indexes = [change objectForKey:NSKeyValueChangeIndexesKey];
  NSLog(@"kind=%d, newValue=%@, oldValue=%@, indexes=%@",
       [kind integerValue], newValue, oldValue, indexes);

And that prints:

2010-11-18 20:01:56.249 Stalled[2692:6f07] kind=1, newValue=(
    "<SquareRootOperation: 0x5f51b40>"
), oldValue=(
), indexes=(null)

2010-11-18 20:01:56.250 Stalled[2692:6f07] kind=1, newValue=(
    "<SquareRootOperation: 0x5f51b40>"
), oldValue=(
    "<SquareRootOperation: 0x5f51b40>"
), indexes=(null)

(SquareRootOperation is simply my subclass of NSOperation that overrides main appropriately, and Stalled is simply the project name.) But note that the method is called twice upon inserting a single operation, and both times with a kind value of 1, which is NSKeyValueChangeSetting, not NSKeyValueChangeInsertion. Additionally, newValue and oldValue seem to be the array itself, not the item added.

Any ideas? Thanks!

Mike Abdullah
  • 14,933
  • 2
  • 50
  • 75
shadowmatter
  • 1,352
  • 2
  • 18
  • 30

2 Answers2

3

The docs say -operations is KVO-compliant, but don't specify to what detail the notifications will be. In practice, it seems you are only told that a change has occurred, so would have to compare the old and new values to find out what was inserted.

Don't forget that these notifications can be sent to you on any thread!

Mike Abdullah
  • 14,933
  • 2
  • 50
  • 75
  • The insert notification will never be sent because the property has type of NSArray (check the documentation) and therefore cannot be inserted into. – JeremyP Jan 04 '11 at 09:01
-1

The operations property of NSOperationQueue does not have a mutable type (it returns NSArray*). It therefore does not implement the indexed to-many compliance methods for mutable arrays so you'll never see the insert events, only the change event for the whole array.

Edit

Shadowmatter has brought up the fact that the actually returned object is an NSMutableArray. This does not, however, change anything. Firstly, Apple's documentation is clear on the issue. If a method is advertised to return an immutable object, you must respect the API. You must not use isKindOf: to find out if it is really mutable and you must definitely not change it.

The API says the operations return type is immutable and you must therefore treat it as such. More importantly for this question, as it's not a mutable collection property, it is not key value coding compliant for the mutable array KVC values. For mutable indexed collection compliance, the class has to

  • Implement one or both of the methods -insertObject:in<Key>AtIndex: or -insert<Key>:atIndexes:.
  • Implement one or both of the methods -removeObjectFrom<Key>AtIndex: or -remove<Key>AtIndexes:.

(taken directly from the Apple KVC guide)

The designer of the NSOperationQueue class designed the operations property as immutable and therefore deliberately ommitted the above methods.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Even though it returns type `NSArray`, the real type of the returned value is the subclass `NSMutableArray`. I've confirmed it by using `isKindOf`. Is this not enough? – shadowmatter Nov 19 '10 at 18:26
  • Thanks for the answer, JeremyP! The More iPhone 3 Development book actually pointed out that, even though the return value was `NSArray`, the actually instance returned was `NSMutableArray` and so you could observe elements being inserted or removed. I assume that at some point Apple fixed this so that the behavior of the class complied with their own documentation, and now the example is broken. It all makes sense now... Thanks again! – shadowmatter Nov 20 '10 at 18:14
  • The array being immutable/mutable has **nothing** to do with KVO-compliance. The docs say `-operations` is KVO-compliant and it is – Mike Abdullah Dec 23 '10 at 17:34
  • @Mike Abdullah: It has everything to do with KVO compliance. If your property is a mutable collection, there are **additional** KVO notifications you can get when things are inserted to or removed from it (as opposed to when the property is reassigned with a new collection). The questioner was trying to use the mutable collection notification on a property that was an immutable type. – JeremyP Dec 24 '10 at 11:25
  • The array is not the object in charge of KVO notifications, so mutable/immutable has no bearing on the notifications sent. I could quite easily implement an API along the lines of `NSOperationQueue`'s, backed by an *immutable* array, that sends detailed insertion/removal notifications. – Mike Abdullah Dec 29 '10 at 11:46
  • @Mike Abdullah: No you couldn't. How do you insert into an **immutable** array? Semantically, it makes no sense. And this is why the operations have not been implemented in this case. The property is advertised as immutable. You don't expect to be able to insert into an immutable object. You don't expect insert notifications. You don't *get* insert notifications. Do you understand now? – JeremyP Dec 29 '10 at 12:59
  • The property is not advertised as immutable as such. There is an `-addOperation:` method! Yes, the returned type of `-operations` is `NSArray`, but I challenge you to find more than a tiny handful of APIs in Cocoa that return `NSMutableArray`. – Mike Abdullah Dec 29 '10 at 18:11
  • You could happily create a property that's backed by an `NSArray` instance. Upon insert, a new array is created (the old array + inserted object), and that replaces the old array. Yes, it's fairly inefficient, but can be useful for properties that are read a lot, but updated rarely. – Mike Abdullah Dec 29 '10 at 18:13
  • @Mike Abdullah: The property is advertised as an NSArray (go and read the official documentation). That means you **must** treat it as an immutable colllection whether it is implemented as a mutable array or not. – JeremyP Jan 04 '11 at 08:54
  • @Mike Abdullah: And your idea of creating a mutable property by replacing an immutable array will not work. If a property is advertised as a mutable collection, then it is reasonable for a client to assume that retrieving a reference to the collection and then getting an insert notification will not invalidate the reference, which your implementation does. – JeremyP Jan 04 '11 at 08:59
  • OK, show me some examples from Cocoa that are "advertised as a mutable collection" – Mike Abdullah Jan 04 '11 at 10:18
  • @Mike: Why? The fact that other properties in Cocoa might by typed as mutable collections would not alter in any way the fact that this one is not. – JeremyP Jan 04 '11 at 10:23
  • 1
    OK, maybe you need to re-read the docs on KVO-compliance. When you observe something like `-operations` you are observing the *property*, **not** the *array* itself. The observed object is free to implement KVO-compliance however it likes. Yes, it's easiest to use the automatic support gained from writing accessor methods backed by an `NSMutableArray`. But that approach is **not** a requirement. – Mike Abdullah Jan 04 '11 at 10:30
  • @Mike: Will you **please** read what I keep telling you. The **property** is typed as an **NSArray**. The **property** is typed as an immutable collection. Read the documentation for NSOperationQueue **please**. You'll see the operations property is declared as `- (NSArray *)operations`. That is why the Apple developers did not see fit to implement the insert/delete notifications. – JeremyP Jan 04 '11 at 10:57
  • 1
    And I assure you, you are wrong. The return type of a method and it's KVO-compliance are **orthogonal**. Indeed a method that returns an `NSMutableArray` will not be KVO-compliant without additional work, since modifying that array directly will not send KVO-notifications. – Mike Abdullah Jan 04 '11 at 16:46
  • @Mike Abdullah: Aha, you are beginning to get there. We are talking about the KVO **insert** notification which only makes sense where the **property** type is a mutable collection. Please acknowledge that you understand that. – JeremyP Jan 04 '11 at 17:12
  • What do you mean by "the property type is a mutable collection"? – Mike Abdullah Jan 04 '11 at 18:55
  • @Mike Abdullah: Even properties are declared. Either there is an `@property (...) someType propertyName;` in the interface or there is a getter (and optionally a setter) method declared e.g. `-(someType) propertyName;`. In both of these cases "someType" is the type of the property. According to the Apple documentation, the declaration of the operations type is `-(NSArray*) operations;` that makes it a collection but not a mutable collection. If it had been `-(NSMutableArray*) operations;` it would be a mutable collection, but it's not,so the insert notification has not been implemented. – JeremyP Jan 05 '11 at 08:52
  • And if I were writing such a class myself, I most definitely would **not** make an `-(NSMutableArray*)operations` method. By writing such a thing, the queue would have no good way to know when operations were added/inserted/remove, and KVO notifications would **not** be sent. To gain those features, quite a bit more custom code would be required. – Mike Abdullah Jan 05 '11 at 10:36
  • More importantly, the docs show how to be KVO-compliant, which strangely enough, do not involve publishing a mutable array: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ModelObjects/Articles/moIntegrating.html – Mike Abdullah Jan 05 '11 at 10:39
  • @Mike Abdullah, you are right in that respect. Look at this document for how to properly implement relationship properties that are KVC/KVO compliant http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueCoding/Concepts/Compliant.html%23//apple_ref/doc/uid/20002172-SW2 – JeremyP Jan 05 '11 at 10:52