原文链接:http://tutuge.me/2015/02/06/Effective-Objective-C-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Item-2/
前言
第二弹来了。
Item 2 - Minimize Importing Headers in Headers
这一节讲的是尽量在一个头文件中减少其它头文件的引入。
头文件
Objective-C和C语言、C++很像,都将类的实现与声明分开,用.h后缀名文件表示声明文件,用.m文件实现类。当要用到这个类的时候,只需要引入头文件即可,至于编译器、运行时是如何知道头文件里面的类对应的实现在哪里,在这了不做说明。一下用例子说明,先看看下面的类:
//Wheel.h
#import <Foundation/Foundation.h>
//车轮类
@interface Wheel : NSObject
@property(copy, nonatomic) NSString *manufacturer;
@property(assign, nonatomic) NSUInteger radius;
@end
第二个类
//Engine.h
#import <Foundation/Foundation.h>
//发动机类
@interface Engine : NSObject
@property(copy, nonatomic) NSString *manufacturer;
@property(assign, nonatomic) NSUInteger power;
@end
然后就是车类Car:
//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类是啥。
那好,我们加上下面两行:
#import "Wheel.h"
#import "Engine.h"
嗯。这样就不会出错了。但是这样真的好吗?Objective-C给我们提供了@class关键字,就是来解决这个问题的。
前置声明(forward declaration)
何为前置声明?看看下面的Car类的头文件例子。
//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++在声明的时候也可以通过如下方式避免重复声明:
#ifndef _WHEEL_H_
#define _WHEEL_H_
//声明内容...
#endif
但是既然Objective-C有@class这种前置声明的办法,为何不用呢。
必须用import的时候
当然,@class这样的前置声明并不能解决一切头文件引入的问题,如下这样的类,就必须要用import:
#import <Foundation/Foundation.h>
//Car 的声明
#import "Car.h"
//Driver protocol的定义
#import "Driver.h"
@interface BigCar : Car <Driver>
@end
是的,当类需要被继承、定义的protocol需要实现的时候,就需要import相关的头文件了(protocol的实现如果跟类的使用者没有关联,可以定义在类的实现文件中的“扩展category”中),当然,还有@protocol这样的protocol的前置声明,怎么用就留给读者自己查阅相关资料了。
总结
大费周章的讲了这么多,其实目的就是一个:尽量少在头文件里面引入其他头文件。
最终的目的就是只暴露最少的细节。
写代码有段时间了,一直都在琢磨这句话,希望读者也能好好体会~