It's a question about good programming techniques with Cocoa.
When you want to call a method on one property of your class, should you use KVC to get the receiver or just put the name of your property?
Example, KVC:
[[self property] myMethod];
Example, simple:
[property myMethod];
Thanks!
Example, KVC:
[[self property] myMethod];
That isn't KVC. The KVC way is:
[[self valueForKey:#"myProperty"] myMethod]
There is no reason to do this when you know the property at compile time; you can just ask for the property value or the ivar value directly. With KVO and (on the Mac) Bindings already implemented, there's not much reason to use KVC directly, as KVO and Bindings use it for you.
Example, simple:
[property myMethod];
That doesn't access the property; it accesses the ivar.
You're only accessing the property when you send an accessor message to the property's holder (self in your examples). It doesn't matter whether you use [self property] or self.property, as they're equivalent; either one is a property message to self, with whatever side effects that implies.
That's the key difference: Hitting the accessor may cause side effects, whereas accessing the ivar directly never will.
Hence, the best practice: Use the property in all your instance methods (as you probably want the accessors' side effects), except in init methods and dealloc, where side effects would be a bad thing. (As a general rule, you should not send messages to a half-initialized or half-deallocked object. The exception is when you explicitly commented the method as being part of your init/dealloc process and therefore wrote it to be safe to use in such circumstances.)
I believe the formal version is technically correct as that will guarantee any side-effects from a funky getter. (To make sure, make a custom getter that includes NSLog("in getter!") and let us know if it works.)
For setting you have to use the [self setProperty:foo]; as property = foo bypasses the setter and can lead to memory leaks.
If it feels more natural to you, the dot notation (e.g., self.property and self.property = foo) is identical to [self property] and [self setProperty:foo].
Related
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).
I have a question about the best coding practice for lazy instantiation.
I have a custom class (MainClass) that consitutes the model of my view controller. One of the properties of MainClass is another custom class (SubClass).
Now let's say I want to acces and set some of the properties of SubClass from my view controller.
Should I lazy instantiate SubClass in MainClass?
Lazy instantiating SubClass in MainClass save me the trouble to check the existence of SubClass (and to create it if it doesn't exist) every time I want to set one of its properties.
On the other hand though I lose the ability to set variables in my views only if SubClass exists. Let me explain better. Let's say I want to set the stringValue of my textfield only if SubClass.name exists. Every time I ask for the existence of SubClass.name the MainClass will lazily instantiate SubClass which is a waste.
What's the best way to proceed?
You need to make up your mind about the aesthetics, if that's what's driving this question, or you need to explain the performance constraints.
Yes, lazy initialization has advantages and disadvantages.
ADVANTAGES
you don't pay for objects you never use
you don't need actually set fields on the object you won't ever use
if you need it, you can build the object at the last minute, which is usually preferable to building it at startup
DISADVANTAGES
(slight) complexity -- especially if you or colleagues aren't accustomed to the idiom
if you forget to call Initialize() or equivalent in an accessor, you may get tricky bugs in some languages, or crashes in others
A hybrid approach is possible. For important tasks, use lazy instantiation:
- (void) display {
[self initialize];
[self display];
}
and for unimportant tasks, simply check for initialization.
- (void) updateCaches {
if ([self isInitialized]) {
[self reloadCachedDataFrom: [self startDatabaseSession]];
}
}
You don't want to build your object just to update its caches, but perhaps, if the object is live, you would like to go ahead and keep the caches warm. So, you see if you've already instantiated the object, and reload the caches only if it already has been set up.
I might be missing something obvious here, but I'm implementing NSCopying on one of my objects. That object has private instance variables that are not exposed via getters, as they shouldn't be used outside the object.
In my implementation of copyWithZone:, I need alloc/init the new instance, but also set up its state to match the current instance. I can obviously access current private state from inside copyWithZone:, but I can't set it into the new object, because there are no accessors for that state.
Is there a standard way around this while still keeping data privacy intact?
Thanks.
First, you should always have getters, even if they're private. Your object should only access even its own ivars using accessors (except in a very small number of cases). This will save you a great deal of suffering over memory management.
Second, Alex's suggestion of using -> is a standard approach, even though this violates the getters rule above. There are a small number of exceptions to that rule, and copy is one of. Using private setters here is still reasonable (and I used to do it that way exclusively), but I've found for various reasons that using -> often works out cleaner.
Be very careful to get your memory management correct. If you need to call [super copyWithZone:], then you should also read up on the complexities of NSCopyObject() and how it impacts you even if you don't use it yourself. I've discussed this at length in "NSCopyObject() considered harmful."
You can access the instance variables of the copy directly. You use the same pointer dereferencing syntax you would use with a struct. So, for example, if your class is this:
#interface MyCopyableClass : NSObject {
int anInstanceVariable;
}
#end
You can do this:
- (id)copyWithZone:(NSZone *)zone {
MyCopyableClass *theCopy = [[[self class] allocWithZone:zone] init];
theCopy->anInstanceVariable = anInstanceVariable;
return theCopy;
}
One option is to create a custom initializer that accepts the private iVar values. So you create it like:
-(id) initWithPropertyOne:(SomeClass *) anObject andPropertyTwo:(SomeClass *) anotherObject;
When you instantiate the copy, just use the custom initializer.
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.
As I learn more about KVO and KVC, I have become curious -
How does NSObject provide automatic KVO when accessing setter methods?
If I create a new object with an accessor named setName,
how does an observer get notified when someon calls
[obj setName:#"Mystery"];
Thanks for any feedback
I always explain to people that "nothing is magic in Cocoa; it's just code." But KVO borders on magic. It's called isa-swizzling. Your class is transformed at runtime (the first time anyone observes you) into a dynamically generated sub-class that overloads all getters and setters. Calls to -class are wired to lie to you and return the old class, so you won't see the magic subclasses except in the debugger if you look directly at the isa pointer.
Noticing that KVO must be bizarre is a major step in Cocoa enlightenment. Congratulations.
Key-Value Observing Implementation Details