我们知道,如果在项目中存在两个同名的类文件(即使所处文件夹不同),会报错“duplicate symbols”。但是如果在不同的文件夹下创建同一个类的扩展并且扩展名相同却是可以的。
这是因为编译器不允许我们在同一个项目中创建两个同名的类,编译时就会主动检查。而对于Category,编译器并不关心是否存在同名扩展或同名方法,Category检查是放在运行期的。Category的使用依赖于开发者按约定来自我规范从而避免造成一些奇怪的问题,一般建议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中添加三个目录:
在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" 会看到如下结果:
因为我们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文件重命名,然后设置Use Header Maps为NO,把directory3目录添加到User Header Search Paths中,此时会输出TestClass.h at: directory3/TestClass.h。因为这个时候在包含当前文件的目录下找不到TestClass文件,就在引用目录下查找,就找到了directory3中。
此时再看编译ViewController时,会发现编译器在directory3的前面加上了-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