I am reading "Core Data Programming Guide". It contains this text:
You must, however, change attribute values in a KVC-compliant fashion.
For example, the following typically represents a programming error:
NSMutableString *mutableString = [NSMutableString stringWithString:#"Stig"];
[newEmployee setFirstName:mutableString];
[mutableString setString:#"Laura"];
For mutable values, you should either transfer ownership of the value
to Core Data, or implement custom accessor methods to always perform a
copy. The previous example may not represent an error if the class
representing the Employee entity declared the firstName property
(copy) (or implemented a custom setFirstName: method that copied the
new value). In this case, after the invocation of setString: (in the
third code line) the value of firstName would then still be “Stig” and
not “Laura”.
Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?
Question regarding copy and programming practice:
From what I have read here:
NSString property: copy or retain?
I understand
that using copy will ensure that firstName is "Stig", not Laura
it is wise to do so because "in almost all cases you want to prevent mutating an object's attributes behind its back"
I would really like to know what is the above quoted text trying to tell us in the context of Core Data. We have to use "copy" anyway whether using Core Data or not. Also, I would be glad if someone could throw more light on point "2" (it is wise to...) above as in what will be the consequences of mutating an object's attributes behind its back?
your "Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?"
mis-matched the point that Apple document wants to explain, I believe.
As Apple document points out, if custom-accessor-method is implemented normally, the default implementation does NOT copy attribute values. If the attribute value may be mutable and implements the NSCopying protocol (as is the case with NSString, for example), you can copy the value in a custom accessor to help preserve encapsulation (for example, in the case where an instance of NSMutableString is passed as a value).
Here is a copying setter snippet
#interface Department : NSManagedObject
{
}
#property(nonatomic, copy) NSString *name;
#end
#implementation Department
#dynamic name;
- (void)setName:(NSString *)newName
{
[self willChangeValueForKey:#"name"];
// NSString implements NSCopying, so copy the attribute value
NSString *newNameCopy = [newName copy];
[self setPrimitiveName:newNameCopy];
[self didChangeValueForKey:#"name"];
} #end
The issue is when to use (and how) immutable values.
Since core data use KVO heavily when detecting changes done to objects, if you use a mutable property that is changed directly through it object and not through the property, CoreData will not detect the change to the object and your changes might not persist to the store.
If you use mutable NSManagedObject attributes, override the setter/getter method and use only them to mutate the underlying object (this mean that you are responsible to let CoreData know that a change did happen to the object, and it must be persisted to the store.
Also, if you use transformable properties for complex objects, you must trigger the change notifications yourself in order for CoreData to realise that a change has occurred, and the object should be re-transformed and saved when the context saves.
I would highly recommend that when it comes to simple objects like strings, you use immutable property values which will force you to go through the object properties and trigger the default KVO notification (copy attributes will also force the KVO notifications).
Related
I have a class which as its properties has a number of NSSet values. The content of these sets is not stored in Core Data, but comes from another source. They are basically ID strings which are unique. No point in filling up Core Data with them as separate entities.
However, as far as I can see it's not possible to store an NSSet as an attribute of an NSManagedObject. I guess I would need to serialise it into binary data by hand, whenever the object gets stored, and deserialise it when it gets retrieved from the persistent store? The same would also apply to storing other collection classes.
Has anybody else ever come across this issue and is able to give some advice?
You can set the attribute type to 'transformable' and then directly store the set into the attribute. Be sure that all of the contents of the set conform to < NSCoding >.
In Xcode, in the Project Navigator open up the project_name.xcdatamodeld source for the model editor and select the entity in question. Choose Editor in the Xcode menu bar and there is a Create NSManagedObject subclass... item. Make the subclass and use it for extra non-Core Data properties. The subclass becomes part of the model in place of the entity. It retains all of the entity's behaviors.
I'm sure this is an complete Noob question... but I've actually never had to deal with this scenario before so I'm a bit befuddled...
Let's say I have a custom object I'll call person, and each person object can have an array of "possessions", a kind of inventory if you will. I would set it up like this:
interface person : NSObject {
NSString *name;
NSMutableArray *posessions;
#property (copy) NSString *name;
#property (copy) NSMutableArray *posessions; // no idea if this is even necessary...
}
Of course, I would also synthesize my properties in the implementation file... Now, in my actual controller object, I would make an instance of my object (or usually an array of instances, but for this example, one will work fine...) as so:
person *aPerson;
I know that to access the persons name, I could make a call like this:
[aPerson setName:#"Bob"];
and to retrieve that name, I might use this:
aVar = [aPerson name];
What I'm stuck on is how exactly would I go about adding or retrieving objects to the NSMutableArray located inside my person class? Let's say I want to use the "count" method for the NSMutable Array.
I've done some trial and error with attempts such as:
[aPerson.posessions count];
[[aPerson posessions] count];
Likewise, to add an object to an array, I have often used:
[someArray addObject:anObject];
but attempts like this haven't worked:
[aPerson.posessions addObject:anObject];
After reading up a bunch and searching the web, I can't seem to find exactly how to interact with this NSMutableArray in my custom class. I'm sure it's something obvious that I'm just not quite getting, and it's become a sort of mental block...
Also, am I correct in synthesizing accessor properties for the NSMutableArray? If so, setX and X don't seem to be quite so obvious with NSMutableArray... unless they simply copy the entire array into a local variable...
Perhaps is this what needs to be done? use the accessor methods to get the entire array, place it in a local variable, make my changes, then use the set accessor method to put the entire array back into my person object?
Can someone enlighten me a bit on the syntax I should be using here?
* EDIT *
I thought I'd add a bit of clarification to this question. My custom objects (in the above example, my person object) are basically database records. I have several databases I am working with in my project, so for example:
Person - a custom sub-class of NSObject containing multiple NSString Objects, as well as Ints and BOOLs.
personDatabase - An Array of Person objects (set up and controlled within my main CONTROLLER object)
All of the set and get methods are called from "Controller".
What I have been attempting to do is to directly access the individual objects contained within the personDatabase from within my Controller object. I have done this by declaring another object this way:
Person *activePerson;
Then, all of my calls are made to the currently active Person record (the one currently selected from the personDatabase), such as:
someOutput = [activePerson name];
etc.
Is there a way to directly access the objects inside the NSMutableArray object inside the activePerson object from my Controller object?
You've specified the 'possessions' property as 'copy'. Therefore, when you write aPerson.possessions you are getting a copy of the possessions array. The call to addObject adds anObject to a new array that is a copy of aPerson's array of possessions. The simplest 'fix' would be to change 'copy' to 'retain' (and probably 'readonly'). [Edit: Wrong; it is 'copy on assign' - not 'copy on read']
However, there is a bigger issues. A person has possessions but how you store them is an implementation detail. When you put NSMutableArray in the public interface you overly restrict your implementation. You might be better served to change the Person interface along the lines of:
#interface Person : NSObject {
#private
NSString *name;
// ...
}
- (Boolean) addPossession: (NSObject *) obj;
- (Boolean) remPossession: (NSObject *) obj;
- (Boolean) hasPossession: (NSObject *) obj;
- (NSArray *) allPossessions;
#end
Then, how you implement these possession methods depends on if you use an array, a set, a linked-list, a tree, a whatever.
In Aaron Hillegass' Cocoa Programming for Mac OS X, the Raiseman application connects a button in Interface Builder (IB) to an NSArrayController with sent action -remove:. In the MyDocument class he implements two KVC methods:
- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index;
- (void)removeObjectFromEmployeesAtIndex:(int)index;
When this button is pressed, the -removeObjectFromEmployeesAtIndex: method is called and the currently selected Person (Model) object is removed from the array.
How does the remove: method used in IB cause the -removeObjectFromEmployeesAtIndex: method to be called?
How do I reproduce this effect with an NSTreeController?
If you want a simple built-in option, then it's only going to create an instance of the class you specified in IB. To create another instance, you're going to need to code it yourself. You should have all the information you need from the Tree Controller to insert the new class into the proper place in the hierarchy. Some diligent searching should give you the code you need.
To attempt to help you understand how the NSArrayController mechanism works, I'll explain the best I can from my knowledge of Objective-C and the runtime. Objective-C is a very dynamic language, and you can dynamically call selectors (methods). Since the NSArrayController knows the name of your class (e.g. "Employee"), its internal implementation probably looks something like the following (or easily could):
NSString *removeSelectorName = [NSString stringWithFormat:#"removeObjectFrom%#sAtIndex:",
self.objectClassName];
SEL removeSelector = NSSelectorFromString(removeSelectorName);
[dataRepresentation performSelector:removeSelector
withObject:[NSNumber numberWithInt:self.selectionIndex];
There are examples of this elsewhere in KVO, as with the +keyPathsForValuesAffecting<Key> method (documentation here), which describes which keys cause another key to be updated. If your key is named fullName and it updates whenever the first or last name changes, you would implement this in your class:
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:
#"firstName",
#"lastName",
nil];
}
Further searching (and this question) turned up this documentation page, which explains the semantics of how that method gets called.
What do I need to do to update a tableView bound to an NSArrayController when a method is called that updates the underlying array? An example might clarify this.
When my application launches, it creates a SubwayTrain. When SubwayTrain is initialised, it creates a single SubwayCar. SubwayCar has a mutable array 'passengers'. When a Subway car is initialised, the passengers array is created, and a couple of People objects are put in (let's say a person with name "ticket collector" and another, named "homeless guy"). These guys are always on the SubwayCar so I create them at initialisation and add them to the passengers array.
During the life of the application people board the car. 'addPassenger' is called on the SubwayCar, with the person passed in as an argument.
I have an NSArrayController bound to subwayTrain.subwayCar.passengers, and at launch my ticket collector and homeless guy show up fine. But when I use [subwayCar addPassenger:], the tableView doesn't update. I have confirmed that the passenger is definitely added to the array, but nothing gets updated in the gui.
What am I likely to be doing wrong? My instinct is that it's KVO related - the array controller doesn't know to update when addPassenger is called (even though addPassenger calls [passengers addObject:]. What could I be getting wrong here - I can post code if it helps.
Thanks to anyone willing to help out.
UPDATE
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it? It seems awfully cumbersome to copy the array, release the old one, and update the copy (as opposed to just adding to the existing array).
I don't know if its considered a bug, but addObject: (and removeObject:atIndex:) don't generate KVO notifications, which is why the array controller/table view isn't getting updated. To be KVO-compliant, use mutableArrayValueForKey:
Example:
[[self mutableArrayValueForKey:#"seatedPlayers"] addObject:person];
You'll also want to implement insertObject:inSeatedPlayersAtIndex: since the default KVO methods are really slow (they create a whole new array, add the object to that array, and set the original array to the new array -- very inefficient)
- (void)insertObject:(id)object inSeatedPlayerAtIndex:(int)index
{
[seatedPlayers insertObject:object atIndex:index];
}
Note that this method will also be called when the array controller adds objects, so its also a nice hook for thinks like registering an undo operation, etc.
I haven't tried this, so I cannot say it works, but wouldn't you get KVO notifications by calling
insertObject:atArrangedObjectIndex:
on the ArrayController?
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it?
First off, it's setSeatedPlayers:, with the colon. That's vitally important in Objective-C.
Using your own setters is the correct way to do it, but you're using the incorrect correct way. It works, but you're still writing more code than you need to.
What you should do is implement set accessors, such as addSeatedPlayersObject:. Then, send yourself that message. This makes adding people a short one-liner:
[self addSeatedPlayersObject:person];
And as long as you follow the KVC-compliant accessor formats, you will get KVO notifications for free, just as you do with setSeatedPlayers:.
The advantages of this over setSeatedPlayers: are:
Your code to mutate the set will be shorter.
Because it's shorter, it will be cleaner.
Using specific set-mutation accessors provides the possibility of specific set-mutation KVO notifications, instead of general the-whole-dang-set-changed notifications.
I also prefer this solution over mutableSetValueForKey:, both for brevity and because it's so easy to misspell the key in that string literal. (Uli Kusterer has a macro to cause a warning when that happens, which is useful when you really do need to talk to KVC or KVO itself.)
The key to the magic of Key Value Observing is in Key Value Compliance. You initially were using a method name addObject: which is only associated with the "unordered accessor pattern" and your property was an indexed property (NSMutableArray). When you changed your property to an unordered property (NSMutableSet) it worked. Consider NSArray or NSMutableArray to be indexed properties and NSSet or NSMutableSet to be unordered properties. You really have to read this section carefully to know what is required to make the magic happen... Key-Value-Compliance. There are some 'Required' methods for the different categories even if you don't plan to use them.
Use willChangeValueForKey: and didChangeValueForKey: wrapped around a change of a member when the change does not appear to cause a KVO notification. This comes in handy when you are directly changing an instance variable.
Use willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey:withSetMutation:usingObjects: wrapped around a change of contents of a collection when the change does not appear to cause a KVO notification.
Use [seatedPlayers setByAddingObject:sp] to make things shorter and to avoid needlessly allocating mutable set.
Overall, I'd do either this:
[self willChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
[seatedPlayers addObject:sp];
[self didChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
or this:
[self setSeatedPlayers:[seatedPlayers setByAddingObject:sp]];
with the latter alternative causing an automatic invocation of the functions listed under 1. First alternative should be better performing.
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.