1. 程式人生 > >在類的頭文件裏盡量少引入其它頭文件 <<Effective Objective-C>>

在類的頭文件裏盡量少引入其它頭文件 <<Effective Objective-C>>

scrip 過程 length int 無法使用 c 語言 dem 在一起 word

與C 和C++ 一樣,Objective-C 也使用“頭文件”(header file) 與“實現文件”(implementation file)來區隔代碼。用Objective-C 語言編寫“類”(class)的標準方式為:以類名做文件名稱,分別創建兩個文件,頭文件後綴用.h,實現文件後綴用.m。

創建好一個類之後,其代碼看上去例如以下所看到的:

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

用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

然而這麽做有個問題,就是在編譯引入了EOCPerson.h 的文件時,EOCEmployer 類並不可見。不便強迫開發人員在引入EOCPerson.h 時必須一並引入EOCEmployer.h。所以,常見的
辦法是在EOCPerson.h 中增加以下這行:
#import "EOCEmployer.h"

這樣的辦法可行,可是不夠優雅。在編譯一個使用了EOCPerson 類的文件時,不須要知道
EOCEmployer 類的所有細節,僅僅須要知道有一個類名叫EOCEmployer 就好。所幸有個辦法
能把這一情況告訴編譯器:
@class EOCEmployer;

這叫做“向前聲明”(forward declaring)該類。如今EOCPerson 的頭文件變成了這樣:

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

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。

假設在各自頭文件裏引入對方的頭文件,則會導致“循環引用”(chicken-and-egg situation)。

當解析當中一個頭文件時,編譯器會發現它引入
了還有一個頭文件,而那個頭文件又回過頭來引用第一個頭文件。

使用#import 而非#include指令盡管不會導致死循環,但卻這意味著兩個類裏有一個無法被正確編譯。

假設不信的話。讀者能夠自己試試。
可是有時候必須要在頭文件裏引入其它頭文件。假設你寫的類繼承自某個超類。則必須引入定義那個超類的頭文件。

同理,假設要聲明你寫的類遵從某個協議(protocol),那麽該協議必須有完整定義,且不能使用向前聲明。向前聲明僅僅能告訴編譯器有某個協議。而此時
編譯器卻要知道該協議中定義的方法。
比如,要從圖形類中繼承一個矩形類,且令其遵循繪制協議:

// EOCRectangle.h
#import "EOCShape.h"
#import "EOCDrawable.h"
@interface EOCRectangle : EOCShape<EOCDrawable>
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
@end

第二條#import是難免的。鑒於此,最好是把協議單獨放在一個頭文件裏。

要是把EOCDrawable 協議放在了某個大的頭文件裏,那麽僅僅要引入此協議,就必然會引入那個頭文
件中的所有內容,如此一來,就像上面說的那樣,會產生相互依賴問題,並且還會增加編譯
時間。
然而有些協議。比如“托付協議”(delegate protocol),就不用單獨寫一個
頭文件了。在那種情況下,協議僅僅有與接受協議托付的類放在一起定義才有意義。此時最好
能在實現文件裏聲明此類實現了該托付協議,並把這段實現代碼放在“ class-continuation 分
類”(class-continuation category)裏。這樣的話。僅僅要在實現文件裏引入包括
托付協議的頭文件就可以,而不需將其放在公共頭文件(public header file)裏。每次在頭文件裏引入其它頭文件之前,都要先問問自己這樣做是否確有必要。

假設能夠用向前聲明代替引入,那麽就不要引入。

若由於要實現屬性、實例變量或者要遵循協議而必須引入頭文件,則應盡量將其移至“ class-continuation 分類”中。

這樣做不僅能夠縮減編譯時間,並且還能減少彼此依賴程度。若是依賴關系過於復雜。則會給維護帶來麻煩,並且,假設僅僅想把代碼的某個部分開放為公共API 的話,太復雜的依賴關系也會出問題。
要點 :

  • 除非確有必要,否則不要引入頭文件。一般來說,應在某個類的頭文件裏使用向前聲
    明來提及別的類,並在實現文件裏引入那些類的頭文件。這樣做能夠盡量減少類之間
    的耦合(coupling)。
  • 有時無法使用向前聲明,比方要聲明某個類遵循一項協議。這樣的情況下,盡量把“該類遵循某協議”的這條聲明移至“ class-continuation 分類”中。

    假設不行的話,就把協議單獨放在一個頭文件裏,然後將其引入。

在類的頭文件裏盡量少引入其它頭文件 &lt;&lt;Effective Objective-C&gt;&gt;