Between teaching an intro to iOS class last week, and Colin Wheeler's blog post "Cover up those ivars", I've been thinking a lot about what we declare in Objective-C, and where we should declare them.
Back in the day--and by that I mean just over the last few years--we had to explicitly declare almost everything. For example, we had to declare our iVars in the header file. The compiler needed to know how much memory our class used when it went to make a subclass.
Now, I don't know about you, but I like to keep my header files as clean as possible. Ideally, they should only contain the properties and methods that outside classes are allowed to call. Everything else should be hidden away. Unfortunately, having the iVars in the header file exposed some of my class's implementation details. Worse yet, if developers could see them, they were likely to try and use them. This made it harder to modify my implementations without also breaking their code.
Over time, however, things changed. If we're using a modern OS (iOS, or a 64-bit version of OS X), we don't need to have our iVars in the header file anymore. We don't even need to explicitly declare them. Xcode can auto-generate appropriate iVars from our property declarations.
Even better, with Xcode 4.4, we no longer need to explicitly call @synthesize for our properties. Again, Xcode will handle it (if necessary). The rules on when Xcode auto generates iVars gets a little more complicated--but generally it does the right thing. If it's not synthesizing methods (for example, because I provided custom getters and setters), it won't generate the iVar.
Class extensions also help us move things out of our header files. We can declare private properties, iVars and methods there. Of course, Objective-C doesn't really offer support for private methods, so developers just make methods private by hiding them. If it's not declared in the header file, it should be considered private.
Now, nothing will stop you from calling the method, if you somehow discover its name--but given Objective-C's dynamic and reflexive nature, there's no way anyone could really stop you anyway. Instead, the public/private divide is really about clearly expressing developer intent. If it's not in the header file, you shouldn't use it, period.
Additionally, like C, we don't actually need to declare our methods. We could simply create a "private" method by just defining them in the @implementation block. As long as our methods are defined before they are called, everything works. Unfortunately, this isn't always possible (e.g. mutually recursive methods).
However, with Xcode 4.4, Apple added considerable support for undeclared private methods. Specifically, the compiler is now smart enough to find the method regardless of its position in the implementation file. So, we no longer need to worry about ordering.
What I Declare
Or, more appropriately, what do I leave undeclared.
I never declare iVars directly. Instead, I will create public or private properties and let them generate the iVars for me. If I need to let a subclass access a private property, I can always re-declare it in the subclass and then use the @dynamic property directive in my @implementation block.
Of course, I get a little worried when I do this. It seems like code smell--an indication that something might be wrong with my design. Why does my subclass need access to that property? Should the property actually be a public property? Should I redesign my class hierarchy? Is there maybe a better way to solve this problem (e.g. using aggregation instead of inheritance)? Most of the time, I find that redeclaring the property is not the correct solution--still, it's nice to know that it's there.
I'm actually a little bit surprised by how many developers continue to manually declare their iVars. In many cases, it looks like they're doing it out of habit…like they're afraid to trust their compiler.
Oddly enough, explicitly declaring iVars can actually introduce bugs. If the iVar and property names don't match up correctly, Xcode will happily create a second iVar for you. The property will connect to the auto-generated iVar, not the one you explicitly declared. While the additional memory usage is unlikely to cause any serious problems, it's at least somewhat sloppy. Besides, worse things happen if you start accessing the iVar directly. Then the code that accesses the iVar and the code that uses the property might end up modifying two different variables. These bugs can be very confusing and hard to find.
I've also stopped using @synthesize. It was always somewhat tedious--especially since I always gave my iVars names that began with an underscore. Now, Xcode does that by default, so it saves me a bit of typing.
Oddly, I still declare private methods in my class extension. This used to be rational. I didn't have to worry about how I ordered my method definitions--which gave me more freedom when it came to organizing my code. Also, Xcode seemed to respond better to explicitly declared methods. Code completion seemed to work better, and I seemed to get better warnings and errors.
However, with Xcode 4.4 none of that seems to matter anymore. I ran a few tests, and Xcode seems to handle undeclared and declared private methods equally well. So, I'm starting to rethink this. I still like the explicit declarations, but I'm worried that I may be clinging to them out of inertia.
Where I Declare it
Basically, whenever possible I try to keep things out of the header file. If other classes need access to it, I put it in the header. If not, I hide it away. If in doubt, I err on the side of hiding things. I can always expose them later.
For properties, it gets a little more complex. For example, many times outside classes may need to read data from my class, but won't actually need to change it. In that case, I typically create a readonly property in the header file, and a readwrite property in the class extension. Inside my class, I can read and write to the property as normal--but other classes can only read the data.
Note, the only attribute you can change when redeclaring properties is the readwrite/readonly attribute. This means we need to include a storage attribute (strong, weak, copy, assign) in our readonly property declarations. Yes, that leaks implementation details--but its not worth losing sleep over.
However, I get a bit extreme when it comes to pulling things from my header files. When I'm using a view controller combined with a storyboard/nib, I will also hide my IBOutlets and IBActions in my class extension.
Now, this gets a bit odd, conceptually. Technically, yes. These are elements that--by definition--need to be accessed outside my class. However, in general, they should only be accessed by the system as it loads the nib (remember, storyboards compile down into nib files). And the system can find it just as easily in my class extension. No one else needs to know about it.
Our view controllers manage one or more views to display data and to respond to user interactions. We shouldn't be concerned with how the view controller handles those tasks. Instead, we should just pass it our data, and let it display the data however it wishes.
Or, think about it this way, if I put outlets in the header file, developers (including my future self, when I'm tired or up against a tight deadline) will be tempted to access and modify the outlets directly. This is almost always a mistake. Again, we can pass the data to the controller or call its public methods--but we should let the controller decide when and how it will display the information.
So, what do you declare, and where do you declare it? More importantly, why?