Lets say I have a simple class like the following:
@interface A {
// @public
int var;
}
// @property(some_property) int var;
@end
When I want to access the variable var, I have some options. If I make var public, I can do this:
A objectA = [ [ A alloc ] init ];
NSLog( @"%d", objectA->var );
objectA->var = someNumber;
If I make it a property instead, I'll have to do something more like this:
A objectA = [ [ A alloc ] init ];
NSLog( @"%d", objectA.var ); // dot-syntax
NSLog( @"%d", [ objectA var ] ); // get-syntax
[ objectA setVar: someNumber ];
I've tried both and they work fine but my question is how dangerous is it to use the old-style pointer notation to access variables inside of an object? Will I have to worry about things later on that I should take care of now by standardizing my accessing of methods? Or can I get away with doing it however I want so long as it works?
Before ARC there was a big difference in that the property-type reference (using either
[thing setMyVar:value];
orthing.myVar = value;
observes retain/release semantics, whereas the direct iVar reference (thing ->myVar = value;
) does not. It's quite a bit more muddled with ARC, however.I'll throw my own 2 cents in since a comment of mine on another question started a similar discussion a few days ago.
You should always use accessor methods to set and get an object's properties outside of the implementation of that object's class. You should almost always use accessor methods to access a class's properties even inside the implementation of said class.
Here is a list of some reasons why:
Encapsulation. A class may expose a property that to the outside world looks just like any other property, but internally is not backed by a corresponding ivar. Perhaps it actually does some transformation of another, internal property. More importantly, this implementation may change sometime down the line. One of the major reasons for encapsulation in OO code is that the internal implementation of a class can be changed without requiring external users of that class to make changes. (I made just such a change -- from an ivar to a completely different backing method -- in an important, and old class just this morning. If a bunch of other classes, or even methods in the same class were doing direct ivar access, I would have had to change a lot more code than I did.)
Memory management. This isn't as big of a deal with ARC, since it will (mostly, see below) be handled properly either way, but with manual memory management, the accessor method for a property will take care of properly retaining and releasing the underlying object. Keeping this code in one place makes for a much lower chance of memory management mistakes (leaks and overreleases), easier to read, easier to modify, less verbose code. Even with ARC enabled, properties declared using copy behavior rely on the setter method to perform the copy, and you bypass that if you do direct ivar access.
Custom behavior on set. This is really just another part of encapsulation. It's very common for a class to want to do something beyond just setting an ivar to a new value when the corresponding setter is called. One very common, simple example is for an NSView subclass to call
[self setNeedsDisplay]
when a property that affects its appearance is changed. If you don't call the setter, instead setting the ivar directly, you will bypass this behavior altogether. Again, you might think it's fine as long as you know that the setter doesn't need to do such a thing, but requirements change, and by using a setter from the start, you make a change down the line much easier.Custom behavior on get/lazy instantiation. One of the most common reasons for this is to do lazy instantiation. You implement the getter method for a property so that it checks to see if the underlying ivar is nil, and if it is, it first initializes the ivar before returning it. The next time it's called, the ivar will be set so the initialization won't happen again. This is an easy way to defer the creation of expensive (CPU intensive and/or memory intensive) objects until and if they are actually needed. For example, it's often done as a performance optimization to improve launch times, which is a perfect example of encapsulation allowing simple improvements to a class's implementation without breaking external code's existing use of a class.
KVO compliance. Objective-C provides a mechanism called Key Value Observing that allows one object to request a notification any time a given property of another object is changed. If you use properly named accessors, or synthesized @properties, you get support for KVO automatically. However, for KVO to work, the accessor methods have to actually be called. If you change an ivar directly, observers of that ivar's corresponding property will not be notified. Whether you're setting another object's property or a property on self, you don't know if observers are registered for changes on that property and you're short circuiting their being notified of the change.
Readability. This doesn't exactly apply to your
objectA->var
example, it's more applicable to direct ivar access in a class's own implementation (var = ...
), whereself->
is implied/inserted by the compiler. The problem is that, particularly in a long method, you may see an assignment statement, and not know at glance if the variable being assigned is local to the current scope, or an instance variable. This can be alleviated with naming conventions like prefixing ivars with an underscore, Hungarian notation, etc., but still Objective-C conventions mean that it's best to useself.var = ...
or[self setVar:...]
(which are exactly the same semantically, by the way).Why not? There are those that don't use accessor methods, but I can't think of any good reasons why. It's not really much faster to type, as prefixing a variable name with 'self.' just doesn't take that much typing. There's a performance penalty involved in calling an accessor since you add an extra ObjC message send. However, this penalty is very small, and of course, premature optimization is a bad thing. It's very rare that the overhead of an accessor method call is enough to seriously impact application performance.
Remember that I used an 'almost' qualifier regarding using direct ivar access inside a class's implementation. If you implement custom accessor methods (as opposed to @synthesizing a property), you will have to directly access the ivar inside the accessors' implementations, of course. Also, there are some who disagree, but it's a good idea (and Apple's recommendation) to directly set ivars inside a class's initializer (
-init
) method(s). The reason for this is that you may want to avoid setter side effects while a class is not completely initialized. For example, you don't want KVO observers to be notified of a change when they may find the notifying object in an inconsistent/partially initialized state.You should use the property form. This approach is more standard, so it will play well in a team. It also has the distinct advantage of allowing you to change the implementation later without changing the interface which callers use.
Yes, using the old-style pointer notation is more dangerous. In Objective-C, using the dot notation is exactly as using the "setVar" method.
When I say "exactly the same", I mean it. When you use objectA.var = someNumber, you are calling the setter method (which you can modify and verify values before modifying instance variables). When using pointer-style assigns, you are modifying directly, allowing to save values that could be wrong, according to the business logic of your application.
In other words: always use Objective-C setter/getter conventions (they are there for some reason).
Me, in particular, like to use the objectA.var = someNumber syntax, because it's easier to understand to other languages programmers (Objective-C method-calls are weird and, if you don't know that Objective-C creates setters/getters automatically when synthesizing properties, it's harder to read for them).
Very dangerous.
Yes. These other things to be worried about include: memory management (Memory. Management!!!), "Key-Value Observing" not working, etc. All this happens because using
->
, you access instance variables directly (some overly strict OO fans will even say that it violates encapsulation, which it indeed does, quite frequently), while the dot notation invokes the proper getter and setter methods (which take care of managing memory decently, notifying KVO listeners, etc., etc.)So, all in all, use accessor methods (and the dot notation if you wish), and don't access instance variables directly.