Effective Objective-C: Minimize Importing Headers in Headers

原创 2013年12月02日 00:02:03

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:

@class EOCEmployer;

This is called forward declaring the class. The resulting header file for EOCPersonwould 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

Image 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.

Image 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.

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Effective Objective-C(第47-52条)系统架构,foundation、for-in、NSTimer

第47条:熟悉系统框架         若是不了解系统架构所提供的内容,那么就可能会把其中已经实现的东西再写一遍。将一系列代码封装为动态库(dynamic library),并在其中放入描述其接口的头...

《Effective Objective-C 2.0》—(第47-52条)—系统架构,foundation、快速遍历for-in、NSTimer

collection使用无缝桥接 快速遍历for-in的使用 谨慎NSTimer的循环引用

Effective Objective-C 2.0:Item 26: Avoid Properties in Categories

Item 26: Avoid Properties in Categories A property is a way of encapsulating data (see Item 6)....

Effective Objective-C 2.0: Item 31: Release References and Clean Up Observation State Only in deallo

Item 31: Release References and Clean Up Observation State Only in dealloc An object going throug...

安装keepalive错误:configure: error: No SO_MARK declaration in headers

安装keepalive的时候,在configure的时候遇到的错误: [root@gw keepalived-1.2.16]# ./configure  checking for gcc... gc...

Custom HTTP Headers in iPhone UIWebView

Custom HTTP Headers in iPhone UIWebView  点击打开链接 If you've done much coding for iPhones, you...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)