Should "to-many" relationships be modelled as properties? - cocoa

After reading the Key-Value Coding Programming Guide, the Key-Value Observing Programming Guide and the Model Object Implementation Guide, as well as reading many StackOverflow entries on the topic and experimenting with various modelling scenarios, I feel like I have a good grasp on how to model my data.
I end up using declared properties for all my attributes and to-one relationships, backed by private ivars. For read-only attributes which need to be privately writeable, I use the readonly attribute in the .h interface declaration, then re-declare the property with the readwrite attribute in a class extension declared in the .m file. Inside the class methods, I always use the property accessors with the dot syntax and never access the private ivars directly.
There is however one aspect which still leaves me puzzled: how to properly model to-many relationships, especially when the collection is to be publicly immutable, but privately mutable (i.e. consumers of the model object cannot add or remove objects to the collection, but the collection's content is managed privately by the class).
I do understand how to implement the KVC accessor methods for to-many relationships (countOf<Key>, objectsIn<Key>AtIndex, etc.) and this is the route I've been following so far.
However, I've seen some sample code that uses declared properties to expose the relationships, do not implement the KVC accessor methods, yet are still Key-Value observable. For example:
#interface MyModel : NSObject
{
// Note that the ivar is a mutable array,
// while the property is declared as an immutable array.
#private NSMutableArray *transactions_;
}
#property (nonatomic, retain, readonly) NSArray transactions;
#end
--------------------
#implementation MyModel
#synthesize transactions = transactions_;
- (void)privateMethodThatManagesTransactions
{
[[self mutableArrayValueForKey:#"transactions"] addObject:t];
}
#end
If a consumer object adds itself as an observer of a MyModel instance for the "transactions" key path, it will be notified whenever transactions are added or removed from the transactions collection (as long as the mutations are done via the mutableArrayValueForKey: method).
To me, this seems like the cleanest way to expose to-many relationships as I don't need to hand-code the collection KVC accessors and it keeps the code clean.
However, it doesn't seem to be the way that is promoted by the Apple documentation, and I can't help but wonder if the fact that it works is only an unreliable side-effect.
So before commiting to one technique or the other in my real-life model classes for a project I'm beginning to work on, I'd like to get the opinion and advice of experienced Cocoa developers.
So the question is: if I use properties to model to-many relationships, do I still need to implement the KVC accessor/mutator methods?
Update
Even when I declare a to-many property as readonly, like in the example above, external code can still call mutableArrayValueForKey:#"transactions" on the model object and mutate the collection. This seems to indicate that using declared properties for to-many relationships isn't the way to go, but I still feel like I don't quite get it...

Yes.
There is however one aspect which still leaves me puzzled: how to properly model to-many relationships, especially when the collection is to be publicly immutable, but privately mutable ….
Easy: Declare the property as readonly in the header, then redeclare it as readwrite, copy in a class extension in the implementation file.
I do understand how to implement the KVC accessor methods for to-many relationships (countOf<Key>, objectsIn<Key>AtIndex, etc.) and this is the route I've been following so far.
There are mutative ones, too. With these, you don't need to use mutableArrayValueForKey:; instead, you can use the mutative accessors directly. You'll still get KVO notifications, because KVO wraps those methods the first time something adds itself as an observer for the property.
I have a list of the accessor selector formats, including the mutative accessors, on my blog.
Edit:
Even when I declare a to-many property as readonly, like in the example above, external code can still call mutableArrayValueForKey:#"transactions" on the model object and mutate the collection.
This is a good reason to make it a habit to use the mutative accessors and avoid mutableArrayValueForKey:. You won't send mutation messages from outside the class if you get a compiler warning (no such [public] method) any time you try it.
Despite the availability of mutableArrayValueForKey: and the risk that someone will use it, KVO-compliant properties are the way to go here.

Related

How is Key-Value Observing implemented internally?

I got answer about Foundation magic for this question: What's the most *simple* way to implement a plain data object which conforms key-value-observing?
What's the magic? How it work internally? Because it's dangerous using framework which I can't understand its internal behavior, I want to know its behavior. Currently, I cannot understand how it work without any method definitions.
Apple's documentation describes how KVO is implemented internally.
The gist of it is that when you register an observer on an object, the framework dynamically creates a subclass of the object's original class, and adjusts the object to appear as an instance of this new dynamic class. You can see this if you inspect an object in the debugger after it has had an observer registered.
This new class intercepts messages to the object and inspects them for those matching certain patterns (such as the getters, setters, and collection access).
In a nutshell: Objective-C 2.0's #property declaration creates accessor methods for the named property, so there are method definitions. #property is just a shorthand way to define them which avoids a lot of repetitious boilerplate code.
When you observe a property, a private subclass is created which implements accessors that call the appropriate notification methods before and after changing the property value. A technique known as "isa swizzling" is then used to change the class of the observed object.

Is this a good KVO-compliant way to model a mutable to-many relationship?

Say I'd like a mutable, unordered to-many relationship. For internal optimization reasons, it'd be best to store this in an NSMutableDictionary rather than an NSMutableSet. But I'd like to keep that implementation detail private.
I'd also like to provide some KVO-compliant accessors, so:
- (NSSet*)things;
- (NSUInteger)countOfThings;
- (void)addThings:(NSSet*)someThings;
- (void)removeThings:(NSSet*)someThings;
Now, it'd be convenient and less evil to provide accessors (private ones, of course, in my implementation file) for the dictionary as well, so:
#interface MYClassWithThings ()
#property (retain) NSMutableDictionary* keyedThings;
#end
This seems good to me! I can use accessors to mess with my keyedThings within the class, but other objects think they're dealing with a mutable, unordered (, unkeyed!) to-many relationship.
I'm concerned that several things I'm doing may be "evil" though, according to good style and Apple approval and whatnot. Have I done anything evil here? (For example, is it wrong not to provide setThings, since the things property is supposedly mutable?)
I wouldn't make a property (even a private one) for the dictionary, but I don't think there's anything wrong with it.
… is it wrong not to provide setThings, since the things property is supposedly mutable?
Yes. KVC will not like the absence of a setThings: method.
Nothing evil here. The only mandatory mutation methods for an unordered relationship are addThings: and removeThings: (see the KVC doc). The accessors for your keyedThings property won't collide with any KVC accessor, so you're also fine there.
To put your mind at ease, the only things Apple's static analyzer is known to check for are messages to undocumented APIs. Other than that, if your implementation decisions don't affect the app's behavior, you're alright for App Store approval.
Update: I got interested in this question and re-read the KVC doc for myself. The language here gave me pause:
To-many unordered relationships are
most often modeled using instance of
NSSet or a subclass. In that case the
key-value coding will, if it doesn’t
find these accessor patterns for the
property, directly access the set.
Typically, you only implement these
methods if you are using a custom
collection class that needs to be
accessed as if it was a set.
It sounds like the author would prefer that you get rid of things and implement enumeratorOfThings: and memberOfThings:.

Can a NSManagedObject own another as a property?

I've taught myself Obj-C, and have been self-teaching Cocoa, but adding Core Data to my program has given me nothing but a huge headache, thanks to me needing extensive custom logic. Here are a couple of the questions that are driving me insane.
What if I want a Managed Object to own another Managed Object? It seems if I give it a to-many relationship, the owned object will simply be shared by various masters, but I want each Owner to have its own.
If I subclass an NSManagedObject, can I make simple calls to the Array Controller to remove or copy instances of ManagedObject, and assume those will be translated into the Core Data model?
If I want to programmatically edit the properties of a ManagedObject, can I get away with mere KVC calls to the Array Controller? What's all this talk about NSPredicate an NSFetchRequest to the NSManagedObjectContext from the NSManagedDataStoreDrivingMeCrazy? Can I make an NSFetchRequest that filters the relationships of an object currently selected in a table view?
Once I use a fetch request to get a group of objects, how do I go about querying their relations? Does that require a whole other FetchRequest, Predicate, and so forth? Isn't Core Data supposed to be easier? Am I missing something?
An entity is similar to a class--it's a blueprint for a managed object that will be instantiated later. Each managed object will have its own attributes and relationships to configure.
You can definitely insert and delete managed objects. You might have to do some code to support copying, but I am not sure.
Yes, the properties (attributes and relationships) of managed objects support KVC (and KVO and bindings).
You can access the object or set of objects simply by using the relationship name that you define in the model (no additional fetch or logic is required).

What is the KVC Search Pattern for mutableArrayValueForKey?

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.

Passing NSMutableArray to other classes

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.

Resources