Effective-Objective-C-读书笔记-Item-2

前言

第二弹来了。

Item 2 - Minimize Importing Headers in Headers

这一节讲的是尽量在一个头文件中减少其它头文件的引入。

头文件

Objective-C和C语言、C++很像,都将类的实现与声明分开,用.h后缀名文件表示声明文件,用.m文件实现类。当要用到这个类的时候,只需要引入头文件即可,至于编译器、运行时是如何知道头文件里面的类对应的实现在哪里,在这了不做说明。一下用例子说明,先看看下面的类:

1
2
3
4
5
6
7
8
//Wheel.h
#import <Foundation/Foundation.h>

//车轮类
@interface Wheel : NSObject
@property(copy, nonatomic) NSString *manufacturer;
@property(assign, nonatomic) NSUInteger radius;
@end

第二个类

1
2
3
4
5
6
7
8
//Engine.h
#import <Foundation/Foundation.h>

//发动机类
@interface Engine : NSObject
@property(copy, nonatomic) NSString *manufacturer;
@property(assign, nonatomic) NSUInteger power;
@end

然后就是车类Car:

1
2
3
4
5
6
7
8
//Car.h
#import <Foundation/Foundation.h>

//汽车类,只有一个轮子,不要奇怪=。=
@interface Car : NSObject
@property(strong, nonatomic) Wheel *wheel;
@property(strong, nonatomic) Engine *engine;
@end

注意到少了什么东西没有?
是的,写过C语言、C++的童鞋一下就能发现,Car类没有引入Engine和Wheel的头文件,肯定会编译出错的,因为编译器不知道Engine、Wheel类是啥。
那好,我们加上下面两行:

1
2
#import "Wheel.h"
#import "Engine.h"

嗯。这样就不会出错了。但是这样真的好吗?Objective-C给我们提供了@class关键字,就是来解决这个问题的。

前置声明(forward declaration)

何为前置声明?看看下面的Car类的头文件例子。

1
2
3
4
5
6
7
8
9
10
11
//Car.h
#import <Foundation/Foundation.h>

//前置声明
@class Wheel;
@class Engine;

@interface Car : NSObject
@property(strong, nonatomic) Wheel *wheel;
@property(strong, nonatomic) Engine *engine;
@end

@class就是类的前置声明(forward declaration),就是告诉编译器“嗨,不用找了,Wheel类和Engine类是肯定存在的,用到的时候再找”。有了前置声明,我们就不用显式的引入Wheel类和Engine类了。

当然,在Car类的实现文件.m文件中,我们还是要显式的引入Wheel和Engine类的,因为在这里我们要具体用到这两个类了,当然要知道类的细节。

为何不要import?

为什么不直接import呢?因为如果直接用import引入Wheel和Engine的声明,那么任何import引入了Car类的文件,也同时会引入Wheel、Engine类的声明,而且最终我们可能并不一定会直接跟Wheel、Engine类打交道,这样不就引入了“没有用”的类了吗?而且这样做很可能造成文件引入成“环”。

虽然import可以避免重复声明造成编译出错,传统的C语言、C++在声明的时候也可以通过如下方式避免重复声明:

1
2
3
4
5
6
#ifndef _WHEEL_H_
#define _WHEEL_H_

//声明内容...

#endif

但是既然Objective-C有@class这种前置声明的办法,为何不用呢。

必须用import的时候

当然,@class这样的前置声明并不能解决一切头文件引入的问题,如下这样的类,就必须要用import:

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>

//Car 的声明
#import "Car.h"

//Driver protocol的定义
#import "Driver.h"

@interface BigCar : Car <Driver>
@end

是的,当类需要被继承、定义的protocol需要实现的时候,就需要import相关的头文件了(protocol的实现如果跟类的使用者没有关联,可以定义在类的实现文件中的“扩展category”中),当然,还有@protocol这样的protocol的前置声明,怎么用就留给读者自己查阅相关资料了。

总结

大费周章的讲了这么多,其实目的就是一个:尽量少在头文件里面引入其他头文件。

最终的目的就是只暴露最少的细节

写代码有段时间了,一直都在琢磨这句话,希望读者也能好好体会~

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器