Smart pointers, who are you?
C++ programmers are familiar with smart pointers, which are lightweight objects acquiring ownership of a resource, usually a slice of memory allocated from the heap. These objects are also responsible of releasing the resource they manage when they are done with it. The "smart" attribute stems from the fact that, thanks to C++ support for operator overloading, it was possible to design objects that have pointer-like semantics. Such objects can then be used as drop-in replacements for raw C pointers. In fact, smart pointers appeared to be so useful they have been added to the C++0x standard (finally C++11?). Many implementations of smart pointers exist, most notably within the standard library itself (the "gimme-a-punch-in-the-head" std::auto_ptr) or within Boost.Smart pointers can exist in C++ thanks to the combination of several language features. First, overloading operator-> and operator* allow to dress up objects with raw pointers clothes. Second, the destructor is the bit of magic by which the responsibility of managing resources can be transferred completely from the programmer to the smart object. Finally, templates are used to generate as many smart pointer flavors as needed to satisfy C++ type constraints. In more advanced implementations (see the wonderful book Modern C++ Design by Andrei Alexandrescu), templates can further be used to design policies allowing to customize a smart pointer behavior (the way it manages ownership, whether it locks resources for concurrent access, etc.)
Smart pointers and Objective-C: An impossible marriage?
Among the several smart pointer flavors often implemented in C++, two are of particular interest from the point of view of an Objective-C programmer:- Reference-counted pointers, which count how many times an object is referenced by the running program. When the counter variable drops to zero, the managed resource is immediately released. Such pointers represent strong references, and may suffer from retain cycle issues: If two objects each maintain a strong reference to each other, they will ultimately collapse into an unreachable object pair, leading to a memory leak. The same problem can also arise if a chain of objects connected by reference-counted pointers closes itself somehow.
- Weak pointers, which have been introduced to solve the retain cycle problem. Such pointers contribute to the reference count, but with the special property that objects only referenced through weak pointers are still destroyed. By identifying the head and the tail of an object chain made of strong references, one can break the cycle by having the tail only store a weak reference to the head. In object pairs, this ultimately reduces to the identification of a parent object which will store the strong reference. The other object then only weakly references the parent one. One could use raw pointers to this purpose, of course, but weak pointers objects offer more robustness against the use of an unreleased resource.
The Objective-C runtime therefore frees the programmer from defining a private instance variable for the reference counter and from calling dealloc when the object is no longer in use, but still requires a careful manual management of the reference counter. Failure to do so leads to crashes or memory leaks, features that should not be part of your project backlog.
Well, this was in fact the situation before Objective-C properties were introduced. In many respects, properties can be seen as the Objective-C counterpart of C++ smart pointers. When assigning an autoreleased object to a property with retain or copy semantics, the programmer is ultimately transferring ownership of the object to the property. When assigning another autoreleased object to the same property, no special care has to be taken since the property will guarantee that the proper semantics is preserved, sending retain, copy or release messages as required. The difference with C++ smart pointers is that the programmer still has to set the property to nil when it is done with the attached object, since no destructor concept is available in Objective-C.
Rules for using properties as smart pointers
In order to use Objective-C properties as smart pointers, apply the following rules:- For each and every object appearing as an instance variable of a class, create an internal instance variable (in the @private section for tighter encapsulation), naming it using some prefix or postfix of your choice. You should avoid leading underscores since they are reserved for system framework implementations (I personally use m_)
- For each and every object instance variable, define an associated property with proper attributes (nonatomic, retain, assign, copy, etc.). The name of the property is simply the name of the instance variable without its prefix or postfix. Strong references are obtained by using the retain attribute, weak ones by using assign
- Never access object instance variables directly, always access them through the associated properties, both for read and write operations
- There is one exception to the previous rule: Object instance variable can (and must!) only be used in the limited scope of the corresponding accessor and mutator implementations (if @synthesize-ing them does not fulfill your needs)
- In the dealloc method of your class, set all object properties you have to nil, which will release them. Also set properties with assign semantics to nil, even if it seems unnecessary: If the semantics is later changed to retain, your code will already be correct. Moreover, if you implemented the mutator by hand, you definitely want to call it, don't you?
- If an object property must be readonly in the public class interface, use a class extension to redeclare a corresponding hidden readwrite property in the non-public implementation file. Note that you have to use extensions, categories won't work (for more information, see Objective-C programming guide). Also use extensions when declaring readwrite properties you do not want to appear in the public interface.
- If you ever need to implement a property accessor or mutator yourself, be sure its implementation matches the semantics of its declaration
The Magic 8-ball example
Consider how you would usually design and implement a Magic 8-ball class without properties. Such a class provides you with a shake method to ask for a new nasty message, a rub method to get a new nice message, and a readonly fortuneMessage accessor for reading the message currently displayed:// MagicEightBall.h @interface MagicEightBall { @private NSString *m_fortuneMessage; } - (void)shake; - (void)rub; - (NSString *)fortuneMessage; @end // MagicEightBall.m @implementation MagicEightBall - (void)dealloc { [m_fortuneMessage release]; [super dealloc]; } // pick... methods below return autoreleased objects - (void)shake { [self playShakeSound]; [m_fortuneMessage release]; m_fortuneMessage = [[self pickNastyRandomMessage] retain]; } - (void)rub { [self playRubSound]; [m_fortuneMessage release]; m_fortuneMessage = [[self pickNiceRandomMessage] retain]; } - (NSString *)fortuneMessage { NSLog(@"Fortune message read"); return m_fortuneMessage; } /* ... */ @endIf you ever forget to release m_fortuneMessage before assigning it a new object, your 8-ball will suffer from a memory leak. If you release it too much it will happily blow in your own hands on the spot.
Now consider how the same class can be implemented using properties:
// MagicEightBall.h @interface MagicEightBall { @private NSString *m_fortuneMessage; } - (void)shake; - (void)rub; // Readonly in public class interface @property (nonatomic, readonly, retain) NSString *fortuneMessage; @end // MagicEightBall.m @interface MagicEightBall () // Readwrite in hidden implementation file, must use class // extension to redeclare the property @property (nonatomic, retain) NSString *fortuneMessage; @end @implementation MagicEightBall - (void)dealloc { self.fortuneMessage = nil; [super dealloc]; } // pick... methods below return autoreleased objects - (void)shake { [self playShakeSound]; self.fortuneMessage = [self pickNastyRandomMessage]; } - (void)rub { [self playRubSound]; self.fortuneMessage = [self pickNiceRandomMessage]; } @synthesize fortuneMessage = m_fortuneMessage; - (NSString *)fortuneMessage { NSLog(@"Fortune message read"); return m_fortuneMessage; } /* ... */ @endProvided the fortuneMessage property accessor and mutator are correctly implemented (and they are if all you do is @synthesize-ing them), no retain or release now ever need to be sent to m_fortuneMessage, except maybe in the mutator implementation. In fact, if you stick to rules listed above, it should now be impossible to over- or under-release m_fortuneMessage.
Benefits
By sticking to the above rules consistently:- The semantics of your properties is not scattered through your implementation anymore, but contained in its declaration (this is very similar to what is achieved using policies in C++ smart pointer implementations, see Modern C++ Design). For example, if you later need to change the semantics of a property from retain to copy, updating its declaration (and maybe its redeclaration if you have one) suffices. If the corresponding setter was not @syntesize-d, its implementation also needs to be updated consistently.
- Access through properties provides you with encapsulation, which is especially good if you later need to update your implementation to perform additional tasks when setting or reading a property. In such cases, manual implementation of the accessor or mutator is required, of course.
- By adding a prefix or postfix to the object member variables, you avoid using them by mistake. Moreover, this prevents name hiding issues. Usually, those are resolved by adding other prefixes (usually 'a', 'an' or 'the'), but this puts more burden on the programmer. Moreover, this often forces you to edit method prototypes borrowed from header files or obtained using autocompletion, which is counter-productive and error-prone (especially if you disabled name hiding warnings, which you didn't, of course)
- It is not required to list member variables in the .h, introducing them at @syntesize-ation point suffices. I find this practice especially convenient to have an overview of all variables, though. This is especially useful when checking that a dealloc method is complete, for example.
- You almost never have to retain or release objects anymore, you can work with autorelease-d objects most of the time. You can still use retain / release pairs to avoid using autorelease locally (e.g. in loops), but this should be the exception, not the rule
All is for the best in the best of all possible worlds (really?)
You can of course argue that:- A readwrite property, even hidden in the implementation file, can still be called. Well, we are talking about Objective-C, a world where almost everything is possible. But in such cases the compiler would warn you (you do NOT ignore warnings, do you?).
- Properties enable KVC and KVO. With KVC, access to hidden properties is made possible (they exist, after all), as discussed just above. With KVO, notifications might also be received when objects are set to nil in dealloc. I personally use KVO with great care, since the best way to make a program workflow difficult to understand is to have notifications thrown around like mad. Most of the time, I prefer to stick with a delegation mechanism, which is far easier to follow. If this still bothers you, you can extend the above rules by allowing prefixed / postfixed object member variables to be used in dealloc as well.
- The semantics of a property can be seen in the public header file. Knowing that an object is retained or copied for a public readonly property does not really make sense, I must admit (since clients are not supposed to be able to use them for assignment, except if they cheat). But at least it does not hurt.
- More "dummy" code has to be written (e.g. @synthesize directives, listing instance variables in the header file). Well, that's what Accessorizer is made for, isn't it?
Dear defagos,
ReplyDeleteI am learning to program for iPhone in Objective-C with Xcode. I am trying not to make all mistakes others have made before me and trying to not make the same mistake twice. So I adopted @properties almost from the start. Today I found a typo in my source that shattered my smugness:
In the .h-file:
@property (strong, nonatomic) UINavigationController *mainNavigationController;
In the .m-file:
@synthesize mainNavigationController;
Later in a method to toggle the toolbar in the same class:
[mainNavigationController setToolbarHidden:![[mainNavigationController toolbar] isHidden] animated:animated];
During debugging it gave me a V variable in the debug window.
I changed that line to:
[[self mainNavigationController] setToolbarHidden:![[[self mainNavigationController] toolbar] isHidden] animated:animated];
I had no warning in the code. The value (pointer / object) of both was the same. Still I feel this was a case of my hiding the property - at least for the debugger.
I feel I should not have used that property without [self ...]. With a private one (declared in the .m-file in the extension) it would not have been possible. What do you think?
Regards,
A. Ophagen