clang是如何查找头文件的

我们知道,如果在项目中存在两个同名的类文件(即使所处文件夹不同),会报错“duplicate symbols”。但是如果在不同的文件夹下创建同一个类的扩展并且扩展名相同却是可以的。

这是因为编译器不允许我们在同一个项目中创建两个同名的类,编译时就会主动检查。而对于Category,编译器并不关心是否存在同名扩展或同名方法,Category检查是放在运行期的。Category的使用依赖于开发者按约定来自我规范从而避免造成一些奇怪的问题,一般建议Category中的方法加上前缀和下划线来避免方法重复。

如果我们项目中不小心造成了存在多个同名的扩展会怎样呢?

Duplicate Category

其中①处的扩展有+log方法,②处的扩展有+log+upload方法。

使用的时候却发现会提示找不到upload方法:

#import "NSObject+Log.h"

- (void)viewDidLoad {
    [super viewDidLoad];
    [NSObject log];//找得到方法
    [NSObject upload];//找不到方法
}

是不是编译顺序导致的呢?在Build phases -> Compile Sources中修改两个同名文件的顺序后发现还是找不到upload方法。

如果把import语句修改为#import "AnotherLog/NSObject+Log.h"就可以改变实际引入的扩展为②处的扩展,就能找到upload方法了。

那么#import "NSObject+Log.h"到底是怎么查找目标文件的呢?

#import <file>用于引入系统头文件,它在一个标准文件系统目录列表中寻找名为file的文件。你可以用-I选项在预处理阶段往目录列表前面添加其他目录(对应Xcode中配置Header Search Paths中添加的目录,编译时就会自动在前面加上-I选项)。
#importt "file"用于引入用户头文件,它首先在包含当前文件的目录下寻找file文件,如果找不到,接着在引用目录下寻找,如果还是找不到,使用与#import <file>方式相同的查找方式寻找。你可以用-iquote选项在预处理阶段往引用目录列表前面添加其他目录(对应Xcode中设置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,都是如此。

比如有这样的目录结构:

目录结构

三个目录下分别定义同名的头文件TestClass.h,每个头文件中都定义一个同名字符串dir,但具体的值不同:static NSString *dir = @"directory/TestClass.h";。在Header Search Paths中添加三个目录:

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,然后执行Product -> Perform Action -> Preprocess "ViewController.m" 会看到如下结果:

Xcode_preprocess_viewcontroller

因为我们import命令在编译时把引用的文件插入到import所在的位置。说明编译器找到了directory3下的TestClass文件。

编译后,在Report Navigator中可以看到在编译ViewController时会自动在添加的目录前面加上-I选项:

编译-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文件重命名,然后设置Use Header Maps为NO,把directory3目录添加到User Header Search Paths中,此时会输出TestClass.h at: directory3/TestClass.h。因为这个时候在包含当前文件的目录下找不到TestClass文件,就在引用目录下查找,就找到了directory3中。

此时再看编译ViewController时,会发现编译器在directory3的前面加上了-iquote选项:

-iquote选项

另外,如果在System Header Search Paths中添加了目录,那么在编译时会自动在目录前面加上-isystem选项,指定-isystem选项的目录会优先于系统目录前被查找。-isysroot选项指定系统目录。

其他概念:

Use Header Maps:这个选项开启后,会为编译器提供一份文本的文件名和相对路径的映射(.hmap文件),我们可以直接引用工程中的文件,而不需要在 Header Search Path 中配置。选项关闭后,你需要把每一个包含头文件的路径添加到目标的header search paths中。

Framework/Library Search Paths:
1、Framework Search Paths
附加到项目中的framework(.framework bundles)的搜索路径,在iOS开发中使用的不是特别多,通常对于iOS的开发来说一般使用系统内置的framework。
2、Library Search Paths
附加到项目中的第三方Library(.a files)的搜索路径,Xcode会自动设置拖拽到Xcode中的.a文件的路径,为了便于移植或者是多人协作开发一般会手动设置。
比如对于设置百度的地图的SDK,我们会设置如下:
$(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(废弃了)
如果设置了Always Search User Paths为YES,编译器会优先搜索User Header Search Paths配置的路径,在这种情况下#include <string.h>,User Header Search Paths搜索目录下面的文件会覆盖系统的头文件。

$(PODS_ROOT)
$(SRCROOT)代表的是项目根目录下
$(PROJECT_DIR)代表的是整个项目
/../上级目录
$(inherited) (target在设置自己路径的时候如果加了这个,那么就是继承project里设置的路径,默认不继承。)

参考:

Build settings reference官方解释
iOS开发中的Search Paths设置
Objective-C 中的 import 和 Search Paths
2.1 Include Syntax

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

推荐阅读更多精彩内容

  • 开发的过程当中,导入第三方库(framework/.a)或者下载使用别人的demo会经常会遇到一些关于库的导入的问...
    lhg_serven阅读 3,805评论 0 8
  • #include #include 是预处理指令,在编译之前的预处理期执行。它的作用是将引入文件中的内容拷贝到当前...
    寒咯阅读 3,566评论 0 10
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,370评论 0 5
  • 模块化工作中,会指定库与库之间的依赖关系,根据依赖关系分层,但随着开发进行,依赖关系又慢慢被破坏。如何让后续的开发...
    donghuan1阅读 3,843评论 0 3
  • 巴山蜀水泪难停, 折翼精灵梦未成。 魂断蓝天金孔雀, 丹心不改碧空情。
    新平_4494阅读 225评论 4 6