Thursday, August 30, 2012

Cover up those ivars

It all started with a tweet a couple mornings ago...

I completely agree with this. Objective-C has evolved over time from where everything was public and had to be included in the header files to now where only the bare minimum needs to be exposed in the header file. Now its completely bad practice to put anything in the header file that doesn't need to be exposed.

There are several Good reasons for this

When designing a class from an external point of view all I should really need to know is what your class name is and what its api's are, what I need to pass them, if anything, and what they return. When you see internal implementation details and variables there is a temptation to use them. This is bad, because you as a consumer of the class should only be concerned with what the class promises to do, and when you reach into the class to access stuff that was never really intended to be exposed you will be bitten with bugs if the implementation details ever change.

I think of methods as fulfilling contracts, I agree that I will pass them what they need if they accept arguments and they agree to return something or cause some effect (i.e. NSLog() piping text to console,etc.) As a writer of classes in a Framework, i've never been concerned about completely changing the details of how the classes work, because I have Unit Tests that test these contracts with my methods to make sure they work as advertised. It's also just cleaner design-wise and the more you can leave out of your header file the better, it makes the header file easier to read and scan for what I am looking for.

How do you do this?

Ideally the rule of thumb should be this: Expose as little as you possibly can in your header file!

This means several things: (1) If you can not declare methods in the header and instead declare them in a class extension in the implementation file(.m) do it (2) If you need to expose properties make them readonly if possible (3) if you need to have ivars declare them right after the @implementation declaration. This doesn't mean hide everything all the time, but generally I like to think of this as designing a class and then only putting in the header file what needs to be exposed to make the class useful.

Here is a class extension

@interface MyClass()
-(NSUInteger)somePrivateMethod;
@end

Here is an example of declaring a method in a class extension. Class extensions are like categories, except that a class extension really just extends the @interface section of a class declaration in another location, so unlike a category if you declare a method and forget to implement it, the compiler will warn you.

If you need to declare properties try to hide them in your extensions, if you need to make them public, make them readonly if possible. This still gives people access to them, but prevents them from doing anything with the variables and screwing up your class. Like so

@interface MyClass : NSObject
@property(readonly, retain) NSString *currentPath;
@end

then in your implementation file you can redeclare it with read & write capabilities. 

@interface MyClass()
@property(readwrite, retain) NSString *currentPath;
@end

This way, publicly you can give people access to bits of information about your class, but only have rights to change them in the implementation. You can also use these extensions to declare properties that are only used in the implementation file.

Another way of declaring variables that didn't exist till very long ago is to do so only after the @implementation declaration like so

@implementation MyClass {
    NSString *internalVar1;
    NSNumber *internalVar2;
}

this is equivalent of 

@interface MyClass : NSObject {
    NSString *internalVar1;
    NSNumber *internalVar2;
}

@end

 except now that this isn't in your header file polluting it. 

How do my Subclasses use these vars?
A legitimate question that someone asked me this afternoon was essentially "If my class hides all these variables in extensions, how could I ever use them in subclasses?" Thats very worth asking. In this case instead of declaring these variables in a class extension in the implementation file you would create a "MyClass_private.h" file where the class extension is. Then your .m file and any interested subclasses can import this header in their implementation files and use it. I would even go so far to write methods into your class such, that to the extent possible, you don't even need to access these internal only vars in your subclasses. 

Why Does this matter if its code just for me?
A couple people asked me this. All I can really ask is why would you write bad code just for yourself? Nobody writes perfect code, well maybe except for me(I kid I kid), but you're not me. But why would you write intentionally bad code for yourself? I always write my classes using these principals, even for stuff thats just for play. Well designed classes means not having to spend tons of time ripping & rebuilding them just to make them presentable later on, it means you can instantly take them and use them in anything without much worry. 

In Conclusion
The less you expose in your headers the cleaner they are, they become easier to read, they don't expose anything that could become future bugs or crashes later on. This makes it easier for you to completely rewrite or change implementation details at will.  

Follow Up
Rich in Comments makes a good point about also putting IBOutlets & IBActions in class extensions. When I said to hide vars and methods I also meant these. The IB component in Xcode iirc didn't see those initially. However for a while now it can see IBOutlets and IBAction methods declared in Class Extensions.

When I said that some of this is "new" in this article, generally I mean in the grand scheme of Objective-C's history at Apple it's new.

Eric made a good point, and I saw and forgot about this in the WWDC videos, that in LLVM Compiler 4.0 its no longer necessary to declare private methods as the compiler scans the implementation for those methods and thus won't give you an error if you forget to declare it.

I was asked if I put my ivars in a class extension or declare after the implementation, generally I declare them in my class extensions.

 
...