1.Workspace和project
workspace包含了project,project包含了target,target是最终的产物,target由Configuration配置文件配置,同时也受Scheme控制编译环境(debug和release等)。
1.1 workspace
新建一个空白的workspace:
新建完成后是一个完全空白的workspace
先看一下 NewWorkspace.xcworkspace 的内容,右键 -> 显示包内容
其中有三个文件:contents.xcworkspacedata、xcshareddata、xcuserdata
- contents.xcworkspacedata
打开后是一个xml文件,里面记录了这个workspace关联了哪些project
由于当前没有关联project,所以是空的。
- xcshareddata 和 xcuserdata
xcshareddata记录了项目中标记为share的Schemes,外部可以使用;xcuserdata记录的就是项目中没有标记为share的Schemes,外部不可使用。
1.2 project
我们在上面的workspace中新建一个project,先新建一个空project
好了之后这个project完全空的,并且跟workspace也没有联系
关闭project,打开workspace,然后左下角点击 + 号,Add Files to "NewWorkspace"
选中project,添加进来
这个时候 workspace 和 project 就联系起来了,只是这个时候还没有target。
现在回过头来看 NewWorkspace.xcworkspace,右键 显示包内容,打开 contents.xcworkspacedata 文件,会发现里面记录了 project 文件的路径。
2.Target和Scheme
2.1 Target
Target是构建项目的目标,这么理解:编译target,得到最终的product。所以target包含了要构建的代码。
在workspace中,选中project之后,点击+
创建完成后就有target了,也有了对应的代码
对于target来说,在 Build Settings 中可以配置编译的一些选项,在Build Phases中配置编译哪些代码、依赖哪些库等。
2.1 Scheme
对于项目来说,管理Target的编译状态以及编译时的传参数,是通过 Scheme
用例:通过切换Scheme来切换编译的产物是 debug环境 或 release环境 新建Scheme:
这样就能实现通过切换Scheme来切换编译的产物是 debug环境 或 release环境。
3.xcode内置Shell环境
Xcode把生成产物需要的参数(Build Settings)例如clang需要的参数,以定义shell环境变量的形式,定义在Xcodel的shel环境中。
什么是环境变量?
PATH罗列出shell搜索用户输入的执行命令所在的目录。
递归检测输入文件是否有变更 USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
3.1 将xcode终端的输出,打印到另一个终端上
查看终端标识符
//终端输入:
tty
得到结果:/dev/ttys001
在xcode的RunScript中,输入 1>tty1 ,1 代表终端的正确的信息,> 代表输出到/dev/ttys001的终端上。
编译项目:
4.Xcode多环境配置
在项目中右键新建config文件
config文件以key-value的格式书写
例如:HEADER_SEARCH_PATHS = "${SRCROOT}/Cat"
可以手动指定应用环境:
HEADER_SEARCH_PATHS[config=Release] = "${SRCROOT}/Cat"
写完之后再 Build Settings 中可以看见其修改的内容
由于 config 文件应用于什么环境下(debug或release)是由Project决定的,所以,在Project中可以修改某个环境,应用哪个config文件
在 build Settings 中,点击 Levels 标签,可以查看目前配置的优先级和正在应用的配置是哪个
Resolved标签代表目前应用的配置,然后 配置的优先级依次是:Target手动配置 > Config文件 > project。
在Target手动配置时,加上 $(inherited) 可以继承Config的配置过来,一起生效。
5.Pods工程探究
xcode Shell中有一个配置,设置为 YES 之后,意味着在Run Script 执行脚本之前,会判断 【input Files、Output Files等】 中的文件有无发生变化,如果有发生变化,才执行脚本,无发生变化就不执行脚本。这么做是为了加快编译速度。
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
cocopods在项目中就是通过这个逻辑来规避:不同开发成员由于pods版本不同而产生冲突。
//diff是对比文件
diff "${PODS_PODFILE_DIR_PATH}/Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null
// $? 是上一句命令的执行结果,如果=0意味着正确,!=0意味着不正确
if [ $? != 0 ] ; then
echo "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation." >&2
//退出
exit 1
//结束if语句
fi
# This output is used by Xcode 'outputs' to avoid re-running this script phase.
//将 "SUCCESS" 写入 Output Files 的第一个文件中
echo "SUCCESS" > "${SCRIPT_OUTPUT_FILE_0}"
如果target A要使用到target B的内容,则target A依赖target B:
- Implicit Dependencies: 隐式依赖。如果target A和B在同一个 project 或者 workspace 下,则Xcode可以自动检测依赖关系。当构建A之前,自动构建B;
- Explicit Dependencies: 显式依赖。需要手动添加依赖关系
新建一个command line tool工程,一般是这样的:
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
但其实它还有一个隐藏的参数,叫 char** env,它的作用是可以访问到shell中的环境变量
int main(int argc, const char * argv[], char** env) {
}
在Scheme中,可以分别向 int argc和char** env传递参数
int argc是代表 char * argv[]数组中有几个参数
5.1 打印 const char * argv[] 中的参数
在shell中:parray 3 argv (这里的数字3代表:打印前x个参数)
argv[]中第一个参数是这个可执行文件存在的目录路径
在代码中打印全部参数
int main(int argc, const char * argv[], char** env) {
for (int i = 0; i < argc; ++i) {
NSLog(@"%@", [NSString stringWithUTF8String:argv[i]]);
}
return 0;
}
5.2 打印char** env中的环境变量
在shell中:parray 3 env
char** env中参数都是环境变量,以 key=value 的格式输出。
在代码中打印全部环境变量
int main(int argc, const char * argv[], char** env) {
//打印环境变量
while (*env){
printf("%s\n", *env++);
}
//或者这么打印
return 0;
}
要拿到某个环境变量的参数,C语言提供了一个函数 getenv
char *env = getenv("CCT_ENV");
5.3 -swift文件
在oc使用swift文件的代码,一般这么干:
定义一个swift文件
@objc
public class CCT:NSObject{
}
然后在oc中:
#import "CTLibrary-Swift.h"
@implementation CTLibrary
- (instancetype)init{
self = [super init];
if(self){
//使用swift类
CCT *cct = [CCT alloc];
}
return self;
}
我们会发现,导入名为 CTLibrary-swift 文件,在工程目录中不存在,而是存在于:
跟 hmap 文件同一个目录的在 DerivedSources 文件夹中。
点开CTLibrary-swift文件:
发现它将swift类,用oc做了一个类声明,所以在oc文件中才能访问到swift类。
那么,如果一个sdk项目,需要把 xxx-swift 提供给外界时,则需要把 xxx-swift 放到头文件目录里面。
于是,可以在 Run Script 写一段脚本:
# copy后接受文件的路径
SWIFT_H_PATH="${SRCROOT}/Swift Compatibility Header/${PRODUCT_NAME}_Swift.h"
# 拷贝文件 ditto + 原路径 + 目标路径
ditto "$DERIVED_FILES_DIR/${PRODUCT_NAME}-Swift.h" "${SWIFT_H_PATH}"
build一下,文件就能拷贝到目标文件夹下了
6.Pbxproj文件探究与修改
新建一个空白的工程(没有target),然后右键 .xcodeproj 显示包内容,打开 project.pbxproj查看:
这里面有许多个 section, 每个section包裹着这个项目的信息。
再到工程中,新建一个target,选择 command line tool:
这个时候再看 project.pbxproj文件:
里面的描述就变了,main.m文件 和target都在里面有描述。
具体来说,.pbxproj 文件包含以下几种key:
- archiveVersion : 当前文件的生成版本
- objectVersion : 当前文件内objects的描述版本
- classes : 占位,没有实际意义
- objects : 字典,以每个object的UUID作为key,object的属性作为value
- rootObject : 当前文件的根object的UUID(isa=PBXProject)
已知的object类型:
KNOWN_ISAS = {
'AbstractObject' => %w(
PBXBuildFile
AbstractBuildPhase
PBXBuildRule
XCBuildConfiguration
XCConfigurationList
PBXContainerItemProxy //用来代指当前project包含的其他project
PBXFileReference
PBXGroup
PBXProject
PBXTargetDependency
PBXReferenceProxy //当前project引用的,相同空间的其他project的文件
AbstractTarget
),
'AbstractBuildPhase' => %w(
PBXCopyFilesBuildPhase
PBXResourcesBuildPhase
PBXSourcesBuildPhase
PBXFrameworksBuildPhase
PBXHeadersBuildPhase
PBXShellScriptBuildPhase
),
'AbstractTarget' => %w(
PBXNativeTarget
PBXAggregateTarget
PBXLegacyTarget
),
'PBXGroup' => %w(
XCVersionGroup
PBXVariantGroup
),
}.freeze