在类的头文件中尽量少引用其他头文件

与C和C++一样,Objective-C也是用“头文件”(header file)与“实现文件”(implementation file)来去个代码。用Objective-C语言编写“类”(class)的标准方式为:以类名做头文件,分别创建2个文件,头文件后缀用.h 实现文件用.m。创建好一个类之后,期待吗看上起如下:


        //EOCPerson.h

        #import <Foundation/Foundation.h>

        @interface EOCPerson : NSObject

        @property (nonatomic, copy) NSString *firstName;

        @property (nonatomic, copy) NSString *lastName;

        @end

        //EOCPersono.m

        #import "EOCPerson.h"

        @Implementation EOCPerson

        // Implementation of methods

        @end

用Objective-C语言编写任何类几乎都要引入Foundation.h。如果不在该类本身引入这个文件的话,那么久要引入与其超类所属框架相对应的“基本头文件”(base header file)。例如,在创建iOS应用程序是,通常会集成UIViewController类。而这些子类的头文件需要引入UIKit.h。

现在看来,EOCPerson类还好。其头文件引入了整个Foundation框架,不过这并没有任何问题。如果此类继承自Foundation框架中的某个类,那么EOCPerson类的使用者(consumer)可能会用到其几类中的许多内容。集成子UIViewController的那些类也是如此,其使用者可能会用到UIKit中的大部分内容。

过段时间,你可能有创建了一个名为EOCEmployer的新类,然后可能觉得每个EOCPerson实例都应该有一个EOCEmployer。于是,直接为其添加一项属性:


//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

然而这么做有个问题,就是编译引入了EOCPersoon.h文件时,EOCEmployer类并不可见,不便强迫开发者在引入EOCPerson.h时必须一并引入EOCEmployer.h,所以,常见的办法是在EOCPerson.h中加入下面这行:


#import "EOCEmployer.h"

这种方法可行,但是不够优雅。在编译一个使用了EOCPerson类的文件时,不需要知道EOCEmployer类的全部细节,只需要知道有一个类名叫EOCEmployer就好。所幸有个办法能把这一情况告诉编译器:


@class EOCEmployer.h

这叫做“向前声明”(forward declaring)该类。现在EOCPerson头文件变成了这样:


//EOCPerson.h

#import  <Foundation/Foundation.h>

@class EOCEmployer.h

@interface EOCPerson : NSObject

@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *lastName;

@property (nonatomic, strong) EOCEmployer *employer;

@end

EOCPerson类的实现文件则需引入EOCEmployer类的头文件,因为若要 使用后者,则必须知道其所有接口细节。于是,时间文件就是:


// EOCPerson.m

#import "EOCPerson.h"

#import "EOCEmployer.h"

@implementation EOCPerson

// Implementation of methods

@end

将引入头文件的时机尽量延后,只在确有需要时才引入,这样就可以减少类的使用者所需引入的头文件数量。假设本例吧EOCEmployer.h引入到了EOCPerson.h,那么只要引入EOCPerson.h,就会一并引入EOCEmployer.h的所有内容。此过程若持续下去,则要引入许多根本用不到的内容,这当然会增加编译时间。

向前声明了解决了两个雷互相引用的问题。假设要为EOCEmployer雷加入新增及删除雇员的方法,那么气头文件中会加入下属定义:


-  (void)addEmployee:(EOCPerson *)person;

-  (void)removeEmployee:(EOCPerson *)person;

此时,若要编辑EOCEmployer,则编译器必须知道EOCPerson这个类,而要编译EOCPerson,则又必须知道EOCEmployer。如果再各自头文件中引入对方的头文件,则会导致循环引用(chichen-and-egg situation)。当接续其中一个头文件是,编译器会发现它引入了另外一个头文件,而那个头文件有回过头来引入第一个头文件。只用#import而非#include指令虽然不会导致死循环,但却这意味着两个类里有一个无法被正常编译。

但是有时候必须要在头文件中引入其他头文件。如果你写的类继承自某个超类,则必须一如定义那个超类的头文件,同理,如果要声明你写的类遵从某个协议(protocol),那么该协议必须有完整定义,切不能使用向前声明。向前声明只能告诉编译器有某个协议,而此时编译器却要知道协议中定义的方法。

//例如,要从图形类中集成一个矩形类,切令其遵循回执协议:


// EOCRectangle.h

#import "EOCShape.h"

#import "EOCDrawable.h"

@interface EOCRectangle : EOCShape<EPCDrawable>

@property (nonatomic, assign) float width;

@property (nonatomic, assign) float height;

@end

第二条#import是难免的。鉴于此,最好是吧协议单独放在一个头文件中。要是把EOCDrawable协议防砸ile某个大的头文件里,那么只要引入此协议,就必定会引入那个头文件中的全部内容,如此一来,就像相面说的那样,会产生互相一来的问题,而且还会增加编译时间。

研二有些协议,例如“委托协议”(delegate protocol),就不用单独写一个头文件了。在那种情况想,协议只有与介绍协议委托的类放在一起定义才有意义。此时最好能在实现文件中声明此类实现了该委托协议,并把这段代码放在“class-continuation分类”里。这样的话,只要咋实现文件中包含委托协议的头文件即可,而不需要将其放在公共头文件了(public header file)里。

每次在头文件引入其他头文件之前,都要先问问自己这样做是否确有必要。如果可以用向前声明取代引入,那么久不要引入。若因为要实现属性、实例变量或者要遵循协议而必须引入头文件,则应尽量将其移至“class-continuation分类”中。这样做不仅可以锁典编译时间,而且还能降低彼此依赖程度。若是依赖关系过于复杂,则会给维护带来麻烦,而且,如果只想把代码的某个部分开放为公共API的话,太复杂的依赖关系也会出问题。

要点:

除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来体积别的类,病在实现文件中引入哪些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。

有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵守某协议”这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引用。

此文章是读《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》学习笔记:

第二条:在类的头文件中尽量少引用其他头文件

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容