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.

14 comments:

Rich said...

I totally agree with you. Hide the implementation details. Of course, in Objective-C nothing is ever really hidden--but the goal is to clearly communicate the public API with other developers (including your future self).

Actually, I try to avoid explicitly declaring ivars at all. In modern Objective-C this can lead to a bug, if the name given in @synthesize (or implicit @synthesize with Xcode 4.4) doesn't exactly match the ivar. The system will happily create two similarly named ivars for you.

Now, that's probably not a huge problem. A little sloppy, but the extra memory usage won't typically cause any real issues--unless you try to access the ivar directly, while the properties are still pointing to their own, separate ivar.

If you need to access private properties in a subclass, you can always re-declare them in the subclass, then make them @dymanic.

On the other hand, I'd argue that accessing private data is at least a little sketchy. It's the same argument as before. We're tightly binding our subclass with its superclass. Specifically, we can no longer make changes to the superclasses implementation without also considering how it will affect the subclass.

At the very least, this would make me stop and consider possible alternatives.

And I'd go even further. I like to declare IBOutlets and IBActions in the class extension. I feel that these are also implementation details, that are best hidden away.

Classes that interact with my view controller shouldn't be concerned with how it displays its data. And, as before, if you put them in the header, people will be tempted to access them directly. It's almost always better to pass data to the view controller through properties, then have the view controller responsible for displaying this data in its views.

-Rich-

lowell said...

This isn't new, it's been around since Xcode 4.2.

Konstantin said...

What is in your opinion the better practice - to write ivars in the @implementation or in the class extension part? Till today I had them all in the class extension, so that both ivars and private properties are in the same place

Éric said...

Also, starting with LLVM compiler 4.0, it's no longer necessary to declare "private" methods in a class extension in the .m file. There is no longer a need to forward-declare methods before invoking them inside a class implementation. If the method exists, the compiler will find it, regardless of where it appears in the implementation file (and will dutifully complain if the method does not actually exist).

Mark Dalrymple said...

@lowell - the auto-synthesize is new in Xcode 4.4. No need to explicitly do @synthesize blah = _blah any more.

@Eric - I can't speak for Colin, but I usually put any explicit ivars in the class extension. In my source files it's in a pretty stable location (right after the headers), while the implementation tends to float around (I might have a batch of constants, or helper classes or functions in between.

If you're doing the "class extension into a header so subclasses can get to it" thing, then purely private ivars would go into to @implementation, and "protected" ones into the class extension.

I've got a posting along those lines over at the big nerd ranch blog, if you want another programmer's perspective :-)

P.S. Colin - Hallelujah! Preach it brother!

Éric said...

@Mark I think your last point was actually aimed at Konstantin. :-) Regardless, I agree with you -- I don't tend to use class extensions as I used to (because of the point I just made in the previous comment), but when I do find a need to declare a private ivar, then I agree with you that I prefer to put it in a class extension (i.e. in a @interface) at the top of the .m file, rather than the top of the class's @implementation. It think it's purely an aesthetic preference though.

Éric said...

Oh, and yes, I also do use the "MyBaseClass+Protected.h" headers from time to time to expose "protected" members (ivars/properties and methods) to subclasses only.

Ardie said...

How about the following way of manipulating read-only properties inside the implementation?

// asdf.h
@interface asdf
@property(readonly, nonatomic) sqlite3 *dbo;
...
@end

// asdf.m

@implementation asdf
@synthesize dbo=_dbo; // _dbo is writable.
...
@end

Éric said...

@Ardie This will not cause a setter to be generated, unlike Colin's example of redefining the @property as read-write in a class extension. The @synthesize adds nothing since it's no longer necessary and a _dbo ivar would have been generated anyway in your example, even if you omit the @synthesize. If you don't mind direct ivar access, then this will work, but in general, I prefer Colin's approach (and refrain from accessing the generated ivars directly).

Rich said...

Konstantin,

I never explicitly declare my ivars. I simply put the property in my class extension, and let the property auto create them for me.

Partially this is to avoid possible bugs, mentioned in my previous comment. However, mostly it's just to avoid the extra typing.

I will, however, explicitly declare private methods in the class extension. At one point this had concrete benefits. The compiler seemed to generate better warnings and errors, and I didn't need to worry about the order in which I declared things.

Now, the ordering no longer matters, and the compiler seems a lot smarter, so its probably just a waste of typing.

Unknown said...

Are you all avare of the @private directive, right?

Colin Wheeler said...

Ramy yes I am aware of it, however you are missing the point. You shouldn't even see stuff that is private in the header as Objective-C stands at the moment.

Ardie said...

Hmm, even though I be able to force outer world to use only getter, there actually exists a matching setter, right? (Because the class extension redeclared a property to be readwrite-able.)

Then how can I keep outer world from accessing the setter by using performSelector:withObject: with the selector of it?

Well, just a quick question.

Colin Wheeler said...

"Hmm, even though I be able to force outer world to use only getter, there actually exists a matching setter, right? (Because the class extension redeclared a property to be readwrite-able.)

Then how can I keep outer world from accessing the setter by using performSelector:withObject: with the selector of it?

Well, just a quick question."

Actually it is a very good question. Technically there is really nothing you can do to prevent that. If you declare something readonly in the public header then in the extension redeclare it readwrite using perform selector will work. However you should view it as a massive code smell and something to be avoided. If the only way you can set something is to use -performSelector:withObject: then something is wrong.

I have classes I use that have readonly properties and readwrite access in the implementation as well as readwrite access to variables only declared in the implementation (declared in a class extension) and if something is hidden away so you don't know it exists or don't have write access to it except through specific methods then there is usually a good reason for this.

Generally you should only expose the bare minimum need to make your class useful, and if you can declare properties readonly then do so.

 
...