I used to be a Java guy. Java, the language of type safety, UIs that stick out like a sore thumb, and a VM that sucks your RAM dry; yes, I was all for it. I loved the simplicity with which you express relationships, the trust you have that compilable code (if written sanely) is safe, the beauty of IoC and unit testing, you name it.
Understandably, I was struck with horror and panic when I realized that my new ambition, iPhone development, would require me stepping away from all that. I soon learned that Objective C, while a beautiful and respectable language in its own league, is nowhere as safe as Java. Everything you do in it is dangerous: Half of the problems are only detectable at run-time, refactoring in and indexing it is a nightmare (mostly because of its inheritly dynamic nature) and there's really no decent IDE for it.
Trade-offs
At the same time, all this playing with fire also adds some compelling new ways of getting things done. Where Java has reflection, Objective C goes so much further in a far cleaner way.
To those that have already dipped their fingers in the Java reflection sauce it's clear what an absolute mess your code soon becomes. The beauty of simplicity that Java can be becomes a horrid web of loosely-coupled constructs that affect each other in most unpredictable ways. Reflection is the name of the Java-antichrist, here to tempts us into ruining everything good and pure about writing in the Java language.
Objective C, being a very flexible language by its very nature, provides a bunch of interesting similar features. Admittedly, though, it does it with style. Categories, dynamic properties, pointers, you name it. They're all very nicely integrated into the language and don't bloat or worsen the type-safety of the code too much. Granted, you don't start off from the same level of type safety either.
All this flexibility comes at a very important price, though. Very little about the code is certain at compile-time. While you can convince the compiler to warn you of most bad practices and eliminate a large part of possible bugs that the Java compiler wouldn't even agree to compile, and while sticking to sane code practices goes a long way, you are never certain that your code won't cause illegal pointer dereferencing, method calls on objects that don't even have those methods or allow buffer overflows. These are things that are almost impossible in Java (provided you aren't tinkering with reflection - and even then there's an impecible exceptions framework with checked exceptions that has your back).
The baddest of the bunch, the nastiest of nasties, the meanest of the meanies, is, still memory (pointer) management. While there are very good rules about what you should and shouldn't be doing, I want to be able to trust that the code I have runs perfectly. I don't want to be called by QA or see customers complain: I want my compiler to complain before anyone else sees the stupid mistake I made late last night.
Enter: Properties
Objective C 2.0 has some very interesting features that help with memory management. Sadly, some are not available in the work I'm doing. Since I'm an iPhone developer, I cannot benefit from the garbage collection, for instance. That is a feature privileged for those blessed Mac developers.
Something that is available to iPhone developers: properties. Properties are the what I have dreamt of since the first week I set foot on Java soil. They are a type-safe, simple and clean way of defining and declaring instance variable accessors and setters. Many languages have a variant of these, sometimes with the same or a different name, except for Java. In Java you're still forced to explicitly declare getters and setters for each and every instance variable that you want to reveal to the outside world in a safe and encapsulated object-oriented type of way.
Not only are they beautifully simple, they also pack a punch: Properly used property declarations are the answer to all your memory management woes.
How properties will save your life
The beauty of properties lies in the fact that the compiler uses them to generate getters and setters that do all the important work for you. The code that is generated depends on the hints you specify in the property definition: The property attributes. These attributes make sure that access to the instance variable that backs the property is done in a insistent and safe way.
We can declare the following attributes on our properties:
readonly/readwrite: Specify whether we need just a getter or also a setter.
nonatomic: Specify whether we need access to the property to be atomic (synchronous) or not.
retain/copy/assign: Specify whether the type of memory management we need to perform on the property.
The way most people use properties is quite straight forward. They declare an instance variable in their class header, add a property to it with the same name and type, specify appropriate attributes and synthesize them in the respective class implementation block.
The beauty of properties is that they provide a central and trustworthy way of declaring how our instance variables must be used. For instance, specifying retain on our property means that every time we assign a value to the property the generated setter will release any existing object assigned to the instance variable and retain the new object before assigning it to the instance variable.
While the use of well-defined properties results in perfectly safe code, problems can still occur when we go behind the property's back. There are two things we can do to mess things up:
Accessing the instance variable directly.
Invoking retain, release, autorelease or, God forbid, dealloc on the object in our property ourselves.
I said "well-defined" because, understandably, using incorrect attributes on our properties will also mess things up. The best example of badly defined properties is NSObject-typed properties with the assign attribute. Objects should always have the retain or copy attribute. Objects with the assign attribute are weak links. That means that our property does not take part in the memory management of the object. As a result, whenever the party that does take part in the memory management of the object decides it's done with it and causes it to get deallocated, our property might still reference the object. The results are predictable: application crash imanent.
Knowing that, the answer to all our worries is: "Hands off of property managed memory!". In practice, this means avoiding the above two at all costs.
You should never access instance variables directly: Use the property provided accessor methods for that. They provide an encapsulated and safe way of accessing our instance variables. There should never be a reason to access the instance variable directly.
You should also never send memory management messages to any objects managed by properties. Properties rely on the managed objects' retain state to be as they set it. Whenever you send messages directly to the managed objects you will break that reliance, and with it, your code.
Asserting the rules
While it's great to know good coding practices, knowing them doesn't assert that our code is safe. It's easy to slip up, especially so when multiple developers work on the same codebase. For this reason, it's important to put into effect code practices that help us make sure we abide by the rules, and help us detect whenever we don't or prevent us from breaking them.
As mentioned before, there are three important things we need to keep in mind: avoiding direct ivar access, avoiding manual memory management and using the correct property attributes. The last two are really a matter of keeping your head when coding: we can't do much to enforce this at compile-time.
To aid us stick to the first, though, I recommend the following code style. It encourages object encapsulation and discourages, but more importantly, makes direct instance variable access very noticable.
We begin with our interface declaration:
Foo.h:
@interface Foo : NSObject {
@private
NSString *_name;
NSArray *_friends;
}
@property (readonly, copy) NSString *name;
@property (readonly, retain) NSArray *friends;
-(id) initWithName:(NSString *) name
andFriends:(NSArray *) friends;
@end
Things to note here are the underscore-prefixed instance variables and the copy/retain attributes on readonly property definitions. We use underscore-prefixed instance variables because the only place where we want to reference our instance variables is when we synthesize our properties. Underscore-prefixing them makes it very unlikely we'll ever accidentally reference them from implementation code and means whenever we do, it stands out like a sore thumb in our code. The copy and retain on readonly properties is to avoid a compiler warning which will be caused by our later redefinition of those properties:
Foo.m:
@interface Foo ()
@property (readwrite, copy) NSString *name;
@property (readwrite, retain) NSArray *friends;
@end
This is a class extension which we put in our implementation file. It overrides the property definitions from earlier making them readwrite instead of readonly. However, the setter will only be visible to the implementation, not to anyone using our class. Considder it a private setter. If you need your setter to be public, however, there's no need for this and you should just make it readwrite in the header file. Now, on to our implementation:
Foo.m:
@implementation Foo
@synthesize name = _name;
@synthesize friends = _friends;
-(id) initWithName:(NSString *) name
andFriends:(NSArray *) friends {
if(!(self = [super init]))
return nil;
self.name = name;
self.friends = friends;
return self;
}
-(void) dealloc {
self.name = nil;
self.friends = nil;
[super dealloc];
}
@end
Notice how in the we need to specify the name of the instance variables that will back our properties in their synthesize declarations. That's because the property name is not the same as the instance variable name. It's also the only place in your code you should reference your instance variables. Beyond that, you'll be using the properties whenever you need to access or set the instance variable's data. Even in the dealloc method you'll be using only just your properties. Setting them to nil will cause the object held by the respective instance variable to get released if the property is set to retain or copy. This means no explicit memory management and centralized memory management configuration per property.


0 comments:
Post a Comment