Item 2: Minimize Importing Headers in Headers
Objective-C, just like C and C++, makes use of header files and implementation files. When a class is written in Objective-C, the standard approach is to create one of each of these files named after the class, suffixed with .h for the header file and .m for the implementation file. When you create a class, it might end up looking like this:
// EOCPerson.h
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@end
// EOCPerson.m
#import "EOCPerson.h"
@implementation EOCPerson
// Implementation of methods
@end
The importing of Foundation.h
is required pretty much for all classes you will ever make in Objective-C. Either that, or you will import the base
header file for the framework in which the class’s superclass lives. For example, if you were creating an iOS application, you would subclass UIViewController
often. These classes’ header files will import UIKit.h
.
As it stands, this class is fine. It imports the entirety of Foundation, but that doesn’t matter. Given that this class inherits from a class that’s part of Foundation, it’s likely that
a large proportion of it will be used by consumers of EOCPerson
. The same goes for a class that inherits from UIViewController
.
Its consumers will make use of most of UIKit
.
As time goes on, you may create a new class called EOCEmployer
. Then you decide that an EOCPerson
instance
should have one of those. So you go ahead and add a property for it:
// EOCPerson.h
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end
A problem with this, though, is that the EOCEmployer
class is not visible when compiling anything that imports EOCPerson.h
.
It would be wrong to mandate that anyone importing EOCPerson.h
must also import EOCEmployer.h
. So the common thing to do
is to add the following at the top of EOCPerson.h
:
#import "EOCEmployer.h"
This would work, but it’s bad practice. To compile anything that uses EOCPerson
,
you don’t need to know the full details about what an EOCEmployer
is. All you need to know is that a class called EOCEmployer
exists.
Fortunately, there is a way to tell the compiler this:
This is called forward declaring the class. The resulting header file for EOCPerson
would
look like this:
// EOCPerson.h
#import <Foundation/Foundation.h>
@class EOCEmployer;
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end
The implementation file for EOCPerson
would then need to import the header file ofEOCEmployer
,
as it would need to know the full interface details of the class in order to use it. So the implementation file would end up looking like this:
// EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"
@implementation EOCPerson
// Implementation of methods
@end
Deferring the import to where it is required enables you to limit the scope of what a consumer
of your class needs to import. In the example, if EOCEmployer.h
were imported in EOCPerson.h
, anything importing EOCPerson.h
would
also import all ofEOCEmployer.h
. If the chain of importing continues, you could end up importing a lot more than you bargained for, which will certainly increase
compile time.
Using forward declaration also alleviates the problem of both classes referring to each other. Consider
what would happen if EOCEmployer
had methods to add and remove employees, defined like this in its header file:
- (void)addEmployee:(EOCPerson*)person;
- (void)removeEmployee:(EOCPerson*)person;
This time, the EOCPerson
class needs to be visible to the compiler, for the same reasons as in the opposite case. However, achieving
this by importing the other header in each header would create a chicken-and-egg situation. When one header is parsed, it imports the other, which
imports the first. The use of #import
rather
than#include
doesn’t
end in an infinite loop but does mean that one of the classes won’t compile correctly. Try it for yourself if you don’t believe me!
Sometimes, though, you need to import a header in a header. You must import the header that defines the superclass from which you are inheriting. Similarly, if you declare any protocols that your class conforms to, they have to be fully defined and not forward declared. The compiler needs to be able to see the methods the protocol defines rather than simply that a protocol does exist from a forward declaration.
For example, suppose that a rectangle class inherits from a shape class and conforms to a protocol allowing it to be drawn:
// EOCRectangle.h
#import "EOCShape.h"
#import "EOCDrawable.h"
@interface EOCRectangle : EOCShape <EOCDrawable>
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
@end
The extra import is unavoidable. For such protocols, it is prudent to put them in their own header file for this reason. If the EOCDrawable
protocol
were part of a larger header file, you’d have to import all of that, thereby creating the same dependency and extra compilation-time problems as described before.
That said, not all protocols, such as delegate protocols (see Item 23), need to go in their own files. In such cases, the protocol makes sense only when defined alongside the class for which it is a delegate. In these cases, it is often best to declare that your class implements the delegate in the class-continuation category (see Item 27). This means that the import of the header containing the delegate protocol can go in the implementation file rather than in the public header file.
When writing an import into a header file, always ask yourself whether it’s really necessary. If the import can be forward declared, prefer that. If the import is for something used in a property, instance variable, or protocol conformance and can be moved to the class-continuation category (see Item 27), prefer that. Doing so will keep compile time as low as possible and reduce interdependency, which can cause problems with maintenance or with exposing only parts of your code in a public APIshould ever you want to do that.
Things to Remember
Always import headers at the very deepest point possible. This usually means forward declaring classes in a header and importing their corresponding headers in an implementation. Doing so avoids coupling classes together as much as possible.
Sometimes, forward declaration is not possible, as when declaring protocol conformance. In such cases, consider moving the protocol-conformance declaration to the class-continuation category, if possible. Otherwise, import a header that defines only the protocol.