I'm attempting to understand Cocoa's Key-Value Coding (KVC) mechanism a little better. I've read Apple's Key-Value Programming Guide but am still a little confused about how certain KVC methods search for keys. Particularly, mutableArrayValueForKey:.
Below I'm going to explain how I understand valueForKey: KVC "getters" to work. Then I'll get to my question regarding mutableArrayValueForKey.
There are seven different "getter" KVC methods:
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
When searching for a Value inside a Property (named myKey), Apple's docs state that valueForKey: searches like this:
Tries -getMyKey, -myKey, and -isMyKey (in that order) inside the receiver
If not found, it attempts these ordered, to-many getters (NSArray):
// Required:
- (NSUInteger)countOfMyKey;
// Requires At Least One:
- (id)objectInMyKeyAtIndex:(NSUInteger)index;
- (NSArray *)myKeyAtIndexes:(NSIndexSet *)indexes;
// Optional (improves performance):
- (void)getMyKey:(KeyClass **)buffer range:(NSRange)inRange;
Next, it attempts these unordered, to-many getters (NSSet):
- (NSUInteger)countOfMyKey;
- (NSEnumerator *)enumeratorOfMyKey;
- (KeyClass *)memberOfMyKey:(KeyClass *)anObject;
Next, it attempts to access Instance Variables directly, assuming YES is returned by accessInstanceVariablesDirectly, in this order: _myKey, _isMyKey, myKey, isMyKey.
Lastly, it gives up and calls the receiving class's - (id)valueForUndefinedKey:(NSString *)key method. Usually an error is raised here.
My question is, what is the search order pattern for mutableArrayValueForKey:?
Apple's docs state this:
Accessor Search Pattern for Ordered
Collections
The default search pattern for
mutableArrayValueForKey: is as
follows:
The receiver's class is searched for a
pair of methods whose names match the
patterns -insertObject:inAtIndex:
and -removeObjectFromAtIndex:
(corresponding to the NSMutableArray
primitive methods
insertObject:atIndex: and
removeObjectAtIndex: respectively), or
methods matching the pattern
-insert:atIndexes: and -removeAtIndexes: (corresponding to the
NSMutableArrayinsertObjects:atIndexes:
and removeObjectsAtIndexes: methods).
If at least one insertion method and
at least one removal method are found
each NSMutableArray message sent to
the collection proxy object will
result in some combination of
-insertObject:inAtIndex:, -removeObjectFromAtIndex:, -insert:atIndexes:, and -removeAtIndexes: messages being sent to the original receiver of
mutableArrayValueForKey:.
...etc...
This makes no sense to me as it's discussing "setter" like methods. mutableArrayValueForKey: returns an NSMutableArray. All of the methods listed above return void, and are used to edit an NSMutableArray, not get it. Example:
- (void)insertMyKey:(KeyClass *)keyObject inMyKeyAtIndex:(NSUInteger)index;
- (void)removeObjectFromMyKeyAtIndex:(NSUInteger)index;
Any idea what Apple is trying to say in their docs, or if this is perhaps an error?
My theory is that mutableArrayValueForKey: is likely taking a similar path as valueForKey: when searching to retrieve a KVC value. I'm just not sure what path that really is.
Thanks for any help you can offer! :)
The NSMutableArray you get back from calling mutableArrayValueForKey: is actually a private subclass of NSMutableArray which overrides normal array methods such as -count, -objectAtIndex:, -insertObject:atIndex:, etc. and calls the corresponding KVC methods on the object the array was retrieved from. It basically acts as a proxy for manipulating the to-many relationship of the object, and it's not something you have to worry about creating or returning yourself. A quick example of usage:
Playlist* aPlaylist;
Track* aTrack;
NSMutableArray* mutableTracks = [aPlaylist mutableArrayValueForKey:#"tracks"];
[mutableTracks insertObject:aTrack atIndex:0];
This piece of code adds a track to the beginning of the playlist. If the Playlist class implements KVC methods for its "tracks" relationship, then calling a method on the mutable array will result in the appropriate method being called on the underlying object. So in this example, when you call insertObject:atIndex: on the array, the array will in turn call insertObjectInTracks:atIndex: on the playlist object, and the track gets added to the playlist's array of tracks.
Now, in this example, of course you could just call insertObjectInTracks:atIndex: directly, but there are several advantages you can get out of using mutableArrayValueForKey: instead.
The array wrapper hides the implementation details of the underlying KVC methods. Implementing the entire list of methods isn't strictly required to be KVC compliant. The Playlist class could just implement -tracks and -setTracks:, and the code above will still work. In this case, instead of calling insertObjectInTracks:atIndex:, the mutable array proxy will create a new array with the object inserted at the beginning, and then just call setTracks: on the Playlist object. This is obviously less efficient, so implementing the full list of KVC methods is usually recommended.
In the case where, instead of a constant string for the key, you instead have a variable, using mutableArrayValueForKey: allows you to manipulate the relationship without having to know the exact names of the methods you have to call. As long as the object is KVC compliant for the key you're using, everything will "just work".
It also lets you use any method that NSMutableArray itself implements, so for example you could use methods that search the array for objects, sort the array, etc. without having to rewrite special versions to deal with the KVC stuff.
Related
I recently learned how to define an isEqualToCustomClass: to compare instances of that class (similar to NSString's isEqualToString:).
Many times in my project I need to compare arrays of these objects. I consider them equal if and only if the counts are the same, and the custom objects at each location are the same (the order must be identical).
What is the correct way to make this available throughout my application? Should I subclass NSArray and add a custom isEqualToArrayOfCustomThings: ? Or a category? Or some other utility class that just takes two such arrays and compares them?
-[NSArray isEqualToArray:] uses the isEqual: method of the array members to test equality with another array. If you prefer, -[NSArray isEqual:] will call through to isEqualToArray:.
All you need for this, then, is that isEqual: be implemented in your class.
N.B.: Apple advises that whenever you implement isEqual: for a class, you must also implement hash such that objects which compare as equal also have identical hashes. Mike Ash goes into some detail on this.
This feels like such a stupid question, but how can I find a string in an NSArray?
I tried using
[array indexOfObjectIdenticalTo:myString]
but that requires the sting to have the same address.
Does anyone have any tips on how to do this?
You want the indexOfObject: method, which looks for the object by sending each object in the array an isEqual: message.
Peter's answer is correct.
One additional note; if you have tons and tons of strings in the array, -indexOfObject: is going to do a linear search. This may prove to be a performance bottleneck for which you should consider using a different container; an NSSet or NSDictionary, possibly (depending on what the strings mean).
Another gotcha is if the strings are all relatively similar and/or relatively long.
Of course, don't bother optimizing anything until you have used the analysis tools to prove that you have a performance issue.
You can use NSOrderSet as the container, the over view in NSOrderedSet Class Reference is below:
NSOrderedSet and its subclass, NSMutableOrderedSet, declare the programmatic interfaces to an ordered collection of objects.
NSOrderedSet declares the programmatic interface for static sets of distinct objects. You >establish a static set’s entries when it’s created, and thereafter the entries can’t be >modified. NSMutableOrderedSet, on the other hand, declares a programmatic interface for >dynamic sets of distinct objects. A dynamic—or mutable—set allows the addition and deletion >of entries at any time, automatically allocating memory as needed.
You can use ordered sets as an alternative to arrays when the order of elements is important >and performance in testing whether an object is contained in the set is a consideration— >testing for membership of an array is slower than testing for membership of a set.
Visit http://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSOrderedSet_Class/Reference/Reference.html
containsObject:
Returns a Boolean value that indicates whether a given object is present in the array.
(BOOL)containsObject:(id)anObject
Parameters
anObject
An object.
Return Value
YES if anObject is present in the array, otherwise NO.
Discussion
This method determines whether anObject is present in the array by sending an isEqual: message to each of the array’s objects (and passing anObject as the parameter to each isEqual: message).
Declared In
NSArray.h
In my app, I have a NSDictionary whose keys should be instances of a subclass of NSManagedObject.
The problem, however, is that NSManagedObject does not implement the NSCopying protocol which means that no Core Data objects / instances of NSManagedObject can be used as dictionary keys even though the -[hash] method works fine for them.
Was should I do?
There are four options:
Use a different object as the dictionary key instead, and lookup from that. [object objectID] or +[NSValue valueWithNonretainedObject:] seem the most obvious
Use CFDictionaryCreateMutable() to create a dictionary with retained keys, rather than copied, instead, and then call CFDictionarySetValue() to store the objects
On OS X or iOS6+, [NSMapTable mapTableWithStrongToStrongObjects] gives you a purely Objective-C equivalent to CFMutableDictionary
Implement NSCopying for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject: is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:… is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary and then call CFDictionarySetValue() for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
#{entity1:data1, #entity2:data2, #entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
#[
#{#"entity":entity1, #"data":data1},
#{#"entity":entity2, #"data":data2},
#{#"entity":entity3, #"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.
I have created an NSMutableArray in the implementation of my class loginController. The mutable array contains a set of strings. I want to pass the mutable array with its objects to other classes within my cocoa-project. What is the best way to pass the array?
The most basic case is your login controller simply handing a snapshot of the array to the other controller. In this case, your login controller will need to have references to instances of the other classes, and it will set some property of those instances to the array. Remember to declare the properties with the copy attribute, so that the receivers don't hold on to your private mutable array.
If you want the other controllers to be able to modify the array, don't let them have your mutable array—that's an invitation to hard-to-find bugs.
Instead, you'll need to implement one property on the login controller, instead of one property on each of the other controllers. The login controller's property should have at least a getter and setter (which you can #synthesize), but you can implement more specific accessor methods for efficiency.
Once you have this property, the other controllers should access the property in a KVO-compliant way. If you implement the specific accessors, they can just use those. Otherwise, they'll need to send mutableArrayValueForKey: to the login controller. When they access the contents of that proxy array, they really access the login controller's array; when they mutate the proxy array, they mutate the login controller's array in turn.
Next comes the actual KVO part. You'll want the other controllers to know when one of them (or the login controller) changes the property. Have each controller (except the login controller) add itself as an observer of the property of the login controller. Remember to have them remove themselves in their -dealloc (or -finalize) methods.
In order for the right notifications to get posted, everything needs to use either accessors or mutableArrayValueForKey:. That goes for the login controller itself, too—it should use its own accessors when mutating the array, instead of messaging the array directly. The only exceptions are in init and dealloc (because the accessor messages would be messages to a half-inited/deallocked object, which will be a problem if you ever make the accessors fancy*).
BTW, it sounds like you may have way too many controllers. See if you can't move some of your logic into model objects instead. That drastically simplifies your code, as Cocoa is designed to work with a model layer. Being controller-heavy is fighting the framework, which makes more work for you.
*By “fancy”, I mean doing things other than or in addition to the normal behavior of a given accessor method. For example, insertObject:in<Foo>AtIndex: normally just tail-calls [<foo> insertObject:atIndex:]; if you insert or store the object somewhere other than in an array in an instance variable, or if you do something else in the same method (such as tell a view that it needs to display), then your accessor method is fancy.
short answer that may not be the best practice:
[otherObject giveArray:[NSArray arrayWithArray:theMutableArray]];
the question is a good one, but not complete... do you just need to pass an array of strings or does the class you are passing to need to modify the array?
In general, it's not a problem to simply pass around an NSMutableArray*, however you need to be careful, because you are just passing a pointer ( so if you retain it somewhere, you need to be aware that the owner or some other class may modify the array ).
generally spoken you would want to use NSMutableArray to dynamically build up an array of objects and when you need to share them, then make a non-mutable copy and pass that along.
NSMutableArray* myArr = [NSMutableArray arrayWithObjects:#"1",#"2",#"3",#"four",nil];
// maybe modify the array here...
NSArray* nonMut = [[myArr copy] autorelease];
[someObject doWork:nonMut];
|K<
I think the pattern that's best for your situation is delegation. Your LoginController shouldn't have to know what class it's sending this data to. Instead, you would implement a LoginControllerDelegate protocol
#protocol LoginControllerDelegate <NSObject>
#optional
- (void)loginController:(LoginController *)loginController didReceiveLoginIDs:(NSArray *)ids;
#end
Then, in your LoginController class, you would implement a delegate property like this:
#property (nonatomic, assign) id <LoginControllerDelegate> delegate;
Then, when you've actually got something to communicate to the delegate, you would write this:
if ([self.delegate respondsToSelector:#selector(loginController:didReceiveLoginIDs:])
[self.delegate loginController:self didReceiveLoginIDs:[NSArray arrayWithArray:loginIDs]];
The object that should receive the login IDs would incorporate the LoginControllerDelegate protocol like this:
#interface SomeOtherClass : NSObject <LoginControllerDelegate>
And you would implement the loginController:didReceiveIDs: method in SomeOtherClass.
This way, instead of your LoginController needing to have intimate knowledge of the other classes in your project, you simply establish a mechanism for sending that data to whatever object is interested in it when it becomes available. If you later change which object should receive the login IDs, you only need to choose a different delegate.
I have an object that implements the indexed accessor methods for a key called contents. In those accessors, I call willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: when I modify the underlying array.
I also have a custom view object that is bound to contents via an NSArrayController. In observeValueForKeyPath:ofObject:change:context: the only value in the change dictionary for the NSKeyValueChangeKindKey I ever see is NSKeyValueChangeSetting. When I'm adding objects to the array, I expect to see NSKeyValueChangeInsertion.
Recreating my view's internal representation of the objects it observes every time I insert a single item -- particularly when I'm bulk loading hundreds of items -- presents quite a performance problem, as you'd imagine. What am I doing wrong that Cocoa seems to think I'm setting a completely new array each time I add or remove a single item?
(Note to all readers: I hate using answers for this, too, but this discussion is too long for comments. The downside, of course, is that it ends up not sorted chronologically. If you don't like it, I suggest you complain to the Stack Overflow admins about comments being length-limited and plain-text-only.)
I don't understand what you mean by implementing array accessors in the view.
Implement accessors, including indexed accessors, for the mutable array property that you've exposed as a binding.
Bindings is built on top of KVO.
And KVC.
All bindings are implemented using observeValueForKeyPath:
Overriding that is one way, sure. The other way is to implement accessors in the object with the bindable property (the view).
My custom view provides a binding that the app binds to an array -- or in this case, an array controller. Accessor methods apply to KVC, not KVO.
Cocoa Bindings will call your view's accessors for you (presumably using KVC). You don't need to implement the KVO observe method (unless, of course, you're using KVO directly).
I know this because I've done it that way. See PRHGradientView in CPU Usage.
Curiously, the documentation doesn't mention this. I'm going to file a documentation bug about it—either I'm doing something fragile or they forgot to mention this very nice feature in the docs.
It absolutely matters that I'm getting a set message on every array update. I wouldn't have posted it as a question if it didn't matter.
There are quite a large number of people who engage in something called “premature optimization”. I have no way of knowing who is one of them without asking.
I have an object that implements the indexed accessor methods for a key called contents. In those accessors, I call willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: when I modify the underlying array.
Don't do that. KVO posts the notifications for you when you receive a message to one of those accessors.
I also have a custom view object that is bound to contents via an NSArrayController. In observeValueForKeyPath:ofObject:change:context: the only value in the change dictionary for the NSKeyValueChangeKindKey I ever see is NSKeyValueChangeSetting. When I'm adding objects to the array, I expect to see NSKeyValueChangeInsertion.
For one thing, why are you using KVO directly? Use bind:toObject:withKeyPath:options: to bind the view's property to the array controller's arrangedObjects (I assume) property, and implement array accessors (including indexed accessors, if you like) in the view.
For another, remember that arrangedObjects is a derived property. The array controller will filter and sort its content array; the result is arrangedObjects. You could argue that permuting the indexes from the original insertion into a new insertion would be a more accurate translation of the first change into the second, but setting the entire arrangedObjects array was probably simpler to implement (something like [self _setArrangedObjects:[[newArray filteredArrayUsingPredicate:self.filterPredicate] sortedArrayUsingDescriptors:self.sortDescriptors]]).
Does it really matter? Have you profiled and found that your app is slow with wholesale array replacement?
If so, you may need to bind the view directly to the array's content property or to the original array on the underlying object, and suffer the loss of free filtering and sorting.
I call the KVO methods manually for reasons outside the scope of this issue. I have disabled automatic observing for this property. I know what I'm doing there.
I don't understand what you mean by implementing array accessors in the view. Bindings is built on top of KVO. All bindings are implemented using observeValueForKeyPath: My custom view provides a binding that the app binds to an array -- or in this case, an array controller. Accessor methods apply to KVC, not KVO.
It absolutely matters that I'm getting a set message on every array update. I wouldn't have posted it as a question if it didn't matter. I call something like
[[modelObject mutableArrayValueForKey:#"contents"] addObjectsFromArray:hundredsOfObjects];
On every insertion, my view observes a whole new array. Since I'm potentially adding hundreds of objects, that's O(N^2) when it should to be O(N). That is completely unacceptable, performance issues aside. But, since you mention it, the view does have to do a fair amount of work to observe a whole new array, which significantly slows down the program.
I can't give up using an array controller because I need the filtering and sorting, and because there's an NSTableView bound to the same controller. I rely on it to keep the sorting and selections in sync.
I solved my problem with a complete hack. I wrote a separate method that calls the KVO methods manually so that only one KVO message is sent. It's a hack, I don't like it, and it still makes my view reread the entire array -- although only once, now -- but it works for now until I figure out a better solution.