
我們知道,如果在項目中存在兩個同名的類文件(即使所處文件夾不同),會報錯“duplicate symbols”。但是如果在不同的文件夾下創(chuàng)建同一個類的擴展并且擴展名相同卻是可以的。
這是因為編譯器不允許我們在同一個項目中創(chuàng)建兩個同名的類,編譯時就會主動檢查。而對于Category,編譯器并不關(guān)心是否存在同名擴展或同名方法,Category檢查是放在運行期的。Category的使用依賴于開發(fā)者按約定來自我規(guī)范從而避免造成一些奇怪的問題,一般建議Category中的方法加上前綴和下劃線來避免方法重復(fù)。
如果我們項目中不小心造成了存在多個同名的擴展會怎樣呢?

其中①處的擴展有+log方法,②處的擴展有+log和+upload方法。
使用的時候卻發(fā)現(xiàn)會提示找不到upload方法:
#import "NSObject+Log.h"
- (void)viewDidLoad {
[super viewDidLoad];
[NSObject log];//找得到方法
[NSObject upload];//找不到方法
}
是不是編譯順序?qū)е碌哪??在Build phases -> Compile Sources中修改兩個同名文件的順序后發(fā)現(xiàn)還是找不到upload方法。
如果把import語句修改為#import "AnotherLog/NSObject+Log.h"就可以改變實際引入的擴展為②處的擴展,就能找到upload方法了。
那么#import "NSObject+Log.h"到底是怎么查找目標文件的呢?
#import <file>用于引入系統(tǒng)頭文件,它在一個標準文件系統(tǒng)目錄列表中尋找名為file的文件。你可以用-I選項在預(yù)處理階段往目錄列表前面添加其他目錄(對應(yīng)Xcode中配置Header Search Paths中添加的目錄,編譯時就會自動在前面加上-I選項)。
#importt "file"用于引入用戶頭文件,它首先在包含當前文件的目錄下尋找file文件,如果找不到,接著在引用目錄下尋找,如果還是找不到,使用與#import <file>方式相同的查找方式尋找。你可以用-iquote選項在預(yù)處理階段往引用目錄列表前面添加其他目錄(對應(yīng)Xcode中設(shè)置Use Header Maps為NO以及配置User Header Search Paths)。
#import <file> 在 Always Search User Paths 為YES的情況下,User Header Search Paths 中目錄的掃描順序排在 Header Search Paths 之前。而 #import "file" 無論 Always Search User Paths 是否 YES,都是如此。
比如有這樣的目錄結(jié)構(gòu):

三個目錄下分別定義同名的頭文件TestClass.h,每個頭文件中都定義一個同名字符串dir,但具體的值不同:static NSString *dir = @"directory/TestClass.h";。在Header Search Paths中添加三個目錄:

在ViewController中添加代碼:
#import "ViewController.h"
#import <TestClass.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"TestClass.h at: %@", dir);
}
@end
這里會輸出:TestClass.h at: directory3/TestClass.h。是因為在Header Search Paths中,我們把directory3放在了第一位。
如果我們在Xcode中選中ViewController.m,然后執(zhí)行Product -> Perform Action -> Preprocess "ViewController.m" 會看到如下結(jié)果:

因為我們import命令在編譯時把引用的文件插入到import所在的位置。說明編譯器找到了directory3下的TestClass文件。
編譯后,在Report Navigator中可以看到在編譯ViewController時會自動在添加的目錄前面加上-I選項:

如果把build后編譯ViewController的指令拷貝出來放在終端中,在后面加上-v選項來打印詳細信息,會看到clang是如何查找目標文件的:


如果改為#import "TestClass.h",則輸出:TestClass.h at: directory/TestClass.h,因為在包含當前的目錄下找到了TestClass文件:
#import "ViewController.h"
#import "TestClass.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"TestClass.h at: %@", dir);
}
@end
如果把當前目錄下的TestClass文件重命名,然后設(shè)置Use Header Maps為NO,把directory3目錄添加到User Header Search Paths中,此時會輸出TestClass.h at: directory3/TestClass.h。因為這個時候在包含當前文件的目錄下找不到TestClass文件,就在引用目錄下查找,就找到了directory3中。

此時再看編譯ViewController時,會發(fā)現(xiàn)編譯器在directory3的前面加上了-iquote選項:

另外,如果在System Header Search Paths中添加了目錄,那么在編譯時會自動在目錄前面加上-isystem選項,指定-isystem選項的目錄會優(yōu)先于系統(tǒng)目錄前被查找。-isysroot選項指定系統(tǒng)目錄。
其他概念:
Use Header Maps:這個選項開啟后,會為編譯器提供一份文本的文件名和相對路徑的映射(.hmap文件),我們可以直接引用工程中的文件,而不需要在 Header Search Path 中配置。選項關(guān)閉后,你需要把每一個包含頭文件的路徑添加到目標的header search paths中。
Framework/Library Search Paths:
1、Framework Search Paths
附加到項目中的framework(.framework bundles)的搜索路徑,在iOS開發(fā)中使用的不是特別多,通常對于iOS的開發(fā)來說一般使用系統(tǒng)內(nèi)置的framework。
2、Library Search Paths
附加到項目中的第三方Library(.a files)的搜索路徑,Xcode會自動設(shè)置拖拽到Xcode中的.a文件的路徑,為了便于移植或者是多人協(xié)作開發(fā)一般會手動設(shè)置。
比如對于設(shè)置百度的地圖的SDK,我們會設(shè)置如下:
$(SRCROOT)/../libs/Release$(EFFECTIVE_PLATFORM_NAME),其中$(SRCROOT)宏代表您的工程文件目錄,$(EFFECTIVE_PLATFORM_NAME)宏代表當前配置是OS還是simulator。
Header Search Path:
1、C/C++頭文件引用
在C/C++中,include是變異指令,在編譯時,編譯器會將相對路徑替換成絕對路徑,因此,頭文件的絕對路徑等同于搜索路徑+相對路徑。
(1)#include <iostream.h>:引用編譯器的類庫路徑下的頭文件
(2)#include "hello.h":引用工程目錄的相對路徑的頭文件
2、(User) Header Search Path
(1)Header Search Path指的是頭文件的搜索路徑。
(2)User Header Search Paths指的是用戶自定義的頭文件的搜索路徑
3、Always Search User Paths(廢棄了)
如果設(shè)置了Always Search User Paths為YES,編譯器會優(yōu)先搜索User Header Search Paths配置的路徑,在這種情況下#include <string.h>,User Header Search Paths搜索目錄下面的文件會覆蓋系統(tǒng)的頭文件。
$(PODS_ROOT)
$(SRCROOT)代表的是項目根目錄下
$(PROJECT_DIR)代表的是整個項目
/../上級目錄
$(inherited) (target在設(shè)置自己路徑的時候如果加了這個,那么就是繼承project里設(shè)置的路徑,默認不繼承。)
參考:
Build settings reference官方解釋
iOS開發(fā)中的Search Paths設(shè)置
Objective-C 中的 import 和 Search Paths
2.1 Include Syntax