1、Xcode整体结构
-
WorkSpace
:工作空间,项目Project 在这个空间中工作(可以多Project) -
Target
:对指定代码和资源文件的具体构建方式;一个Target对应生产一个产物(iPhone、iPad产物等) -
Configuration
:产物配置,项目Project需要通过Config管理Target -
Scheme
:对指定Target的环境配置
小结
- Xcode在WorkSpace工作区中 可以有多个Project项目
- 每个Project通过 Configuration配置 可生成 一个对应的Target产物
2、WorkSpace
和 Project
- 我们 所有的项目工程都是通过 WorkSpace 进行管理,即使明面上没有 .xcworkspace文件 的项目,我们只需 显示包内容 就能看到
2.1、生成WorkSpace
contents.xcworkspacedata
:workspace的配置信息,XML格式,包含Project的路径等内容xcshareddata
:Scheme分享给别人看的配置(Product --> Scheme --> Manage Schemes)xcuserdata
:私有的配置
2.2、生成Project并与WorkSpace关联
-
我们新建一个空project,路径与上边workspace文件相同
-
此时打开worksapce还是没有project,因为没有将两者进行关联;我们 在workspace 中将 project 进行添加
-
添加好后再打开workspace的 contents.xcworkspacedata 文件可以看到project路径被添加进来了(以workspace文件为基准路径)
self: -> 在.xcworkspace所在目录下有同名,并以.pbxproj结尾的文件 group: -> 指定目录下.xcodeproj结尾文件的路径 container: -> 在.xcworkspace当前目录下有不同名,但以.xcodeproj结尾文件 absolute: -> 绝对路径 workspace中的所有project都构建在同一个目录中
2.3、Project中添加Target
-
光有空的project并不能完成代码编写,还是空荡荡的,此时我们要新建target
-
建好了target,项目中就有我们熟悉的代码编写的 AppDelegate、ViewController 和 Build setting、Build Phases 这些东西了
2.4、Configuration产出环境
-
有了target我们可以进行产物产出,但我们知道target产出时需要配合Configuration 的不同产出环境,默认是有
Debug
与Release
两种,可我们也可以对其进行增加环境,来适应更多的环境配置
-
添加后我们在Build Setting等地方配置产出环境时就不止 Debug 和 Release 两种了
3、Target 和 Scheme
- Scheme 定义了各个 action 应用的
Target集合
、以及 要使用的配置以及环境变量等
-
Scheme 拥有 多种 Action:Build、Analyze、Archive、Execution、Launch、Profile、Test 等
-
每个Action 可以展开,我们可以在 执行每个Action前与后
插入脚本
和 发送邮件 -
我们可以新建Scheme,并指定其运行环境为Release,这样 同一个Target只要在上边切换Scheme就可简单的运行在不同的配置环境中
-
Scheme 存储在project包中,在哪个文件夹取决于是否设置为
share
因为演示工程的Scheme都是share的,所以我们能在 xcsharedata 文件夹中看到两个Scheme的文件
-
终端直接生成产物命令
xcodebuild -workspace workspace名字.xcworkspace -scheme Scheme名字 -showBuildSettings -json xcodebuild -project Project名字.xcodeproj -scheme Scheme名字 -showBuildSettings -json xcodebuild -project Project名字.xcodeproj -target Target名字 -showBuildSettings -json xcodebuild -project Project名字.xcodeproj -scheme Scheme名字 showBuildSettings -json -configuration Debug -destination generic/platform="iOS Simulator"
3.1、Scheme配置多环境
第3点中说到可以进行多环境配置,我们就演示一下使用场景:比如 不同环境有不同的Host Url:
-
自定义Beta产出环境Configuration
-
建立个用于Beta环境的Scheme
-
配置该Scheme的产出环境为Beta
-
向
User-Defined
中添加自定义的 Host_Url字段,并根据不同的产出环境设置不同的url- 我们可以看到,User-Defined中这些已有内容是下文 4.4 部分创建的用于修改环境变量的
.xcconfig文件
配置后自动产生的,config文件中写入环境变量User-Defined就跟着新增、改变,这里提前提一下
- 我们可以看到,User-Defined中这些已有内容是下文 4.4 部分创建的用于修改环境变量的
-
在info.plist文件中增加对应的HOST_URL字段,并 取HOST_Url的值(各环境的AppIcon、app名称、bundle identifier也都是可以配置的)
-
在需要的地方通过plist文件进行使用
NSString *path = [NSBundle.mainBundle pathForResource:@"Info" ofType:@"plist"]; NSDictionary *infoDic = [[NSDictionary alloc] initWithContentsOfFile:path]; NSLog(@"%@",infoDic[@"HOST_Url"]);
3.2、多Target配置多环境
通过多Target对不同环境设置不同的值
- 复制Target,在Target项与Scheme中对其改名
- 在
Preprocessor Macros
中对不同环境添加 同名的宏(图中宏命名为Development)- 对原Target,这个宏在Debug模式下设置为0,Release模式下设置为1
- 对copy的Target,这个宏,在Debug模式下设置为2,Release模式下设置为3
- 对原Target,这个宏在Debug模式下设置为0,Release模式下设置为1
- 调整2个Target的Scheme运行的环境
- 在适当的地方使用,例如:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { #if Development == 1 NSLog(@"我是MulTarget的Release环境"); #elif Development == 2 NSLog(@"我是MulTarget-Dev的Debug环境"); #elif Development == 3 NSLog(@"我是MulTarget-Dev的Release环境"); #else NSLog(@"我是MulTarget的Debug环境"); #endif return YES; }
- 创建给不同Target用的AppIcon,再在不同Target中进行设置
- 这样,就可以根据多个Target在对应Scheme中配置的运行环境不同,而得到不同的宏值
注意:
- 多Target会有多个info.plist,要注意配置
- Swift在 Other Swift Flags 中配置
- Scheme适合做多环境,多Target更适合做马甲包
- 多Target环境配置
- 多Target开发相似APP
小结
-
Target没有Scheme依然可以编译成功
-
Scheme使项目可以在不同的运行Action(Build、Run、Test、Analyze等)与时机(Action前、Action后)下 配置脚本
-
Scheme可以
为项目预置运行的环境等配置
,从而使开发者 快速、便捷的切换环境、参数等,从而避免为了个环境反复修改参数 -
因为Scheme有默认的配置,因此赋予了workspace和project生成产物的执行能力(最终还是使用target生成,但对workspace执行命令不会报错了,遇到未配置的内容Scheme自动使用默认)
4、Xcode内置环境
- Xcode就是一个终端,它将生成产物所需要的参数(Build Settings)、Clang需要的参数,在Xcode的Shell环境中定义成环境变量(Mac中同时存在多个终端Shell环境)
4.1、产物产出路径
4.2、环境变量
- 环境变量会使用关键字
Export
修饰,以便外界使用
-
Xcode会将 Build Settings 中的编译配置导出,转换成
环境变量
(各种配置项在右侧栏的?选项
中能找到 对应的环境变量名) -
验证打印环境变量(使用JS输出环境变量)
- 能看到打印的内容与自己做的标记:"------LZTargetJS"
- 如果展开看详情可以看到大量
export:环境变量
,这都是根据Xcode中的各种配置转换生成的
- 能看到打印的内容与自己做的标记:"------LZTargetJS"
重要:Xcode内置常用环境变量
$(PROJECT_DIR)
:代表的是整个项目${SRCROOT}
:表示当前工程所在目录
${PODS_ROOT}
:代表的是pod目录${BUILD_DIR}
:Products目录${LOAD_PATH}
:project使用的所有三方库的路径$(inherited)
:继承上一级或依赖项的配置,让当前变量继承变量原有值- 通过CocoaPods集成的项目,$(inherited) 将会包含 Pods.xcodeproj 中的配置
- target在设置自己路径的时候如果加了这个,那么就是 继承project里设置的路径,如果不需要继承就不加,要不然乱加有可能整混导致路径错误
recursive(会在相应的目录递归搜索文件)
non-recursive (非递归)
4.3、跨终端Shell输出
-
每个终端都有其唯一的标识符,可以通过终端命令
% tty
,来获取当前终端标识符,通过该标识符可以进行跨终端操作 -
将脚本输出内容重定位到
已打开的终端
上(坑:不点开终端运行会报错...)
- 1>/dev/ttys000
- 1:表示正确
- >:表示
进行重定位
- /dev/ttys000:已打开的终端唯一标识符
- 原本应该打印在Xcode上的内容转为打印到终端窗口上
- 1>/dev/ttys000
4.4、修改环境变量
.xcconfig
:专门管理和配置Xcode环境变量的文件
-
新建
Configuration Settings
文件 -
在 Config文件 中对环境变量进行配置(可通过include关键字导入其他Config内的配置:
#include "Debug.xcconfig"
) -
此时在 Build Settings 中并不能看到有什么改变,因为在一开始的图中我们就看到,
Configuration 是 Project 中用于配置 Target 的
,因此 我们需要在 Project 中将新建的 Config 文件应用进去(例中只对Project的Debug环境进行应用Config文件) -
完成上面的步骤,我们就能看到 Build Settings 中的对应项被修改为我们Config文件中的内容
-
Build Settings 中手动配置的优先级高于 Config文件
可以使用
$(inherited)
继承使 Config文件 生效 -
还可以进行条件化配置(例子中设置只在Debug模式下)
4.5、自定义环境变量
-
在Config文件中可以自定义环境变量,要注意
// 会当做注释符
所以要做处理(引号也会被保留,不需要引号就不用添加) -
在脚本中输出测试一下自定义的环境变量
-
此时虽然能在脚本中调用,但是并不能在代码中使用,如果想要使用可以配置在Info.plist中,然后在 代码中将 info.plist 转成字典后再使用
上图我们可以看到,Info.plist 文件中很多内容其实都是 对环境变量的取值
- (void)viewDidLoad { [super viewDidLoad]; // 方法一,获取文件的全部路径, 解析 info.plist NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Info.plist" ofType:nil]; NSDictionary *infoDic = [NSDictionary dictionaryWithContentsOfFile:filePath]; //方法二 直接获取 NSDictionary *infoDic2 = [NSBundle mainBundle].infoDictionary; NSLog(@"LZ_URL: %@",infoDic[@"LZ_URL"]); NSLog(@"LZ_URL: %@",infoDic2[@"LZ_URL"]); } // 打印结果 LZTarget[2156:91612] LZ_URL: http://www.baidu.com LZTarget[2156:91612] LZ_URL: http://www.baidu.com
4.6、创建命令
-
编写命令输出 Mach-O 文件路径
-
也是通过
${变量}
来执行命令
5、Shell脚本比对文件内容
5.1、Shell常用命令介绍
5.1.1、基础符号
符号 | 作用 |
---|---|
> | 重定向 |
& | 引用 |
0 | stdin 标准输入 |
1 | stdout 标准输出 |
2 | stderr 标准错误 |
5.1.2、常用指令
$ 指令 | 作用 |
---|---|
$0 | 当前脚本文件名 |
$n | 传递给脚本或函数的参数;n是数字,表示第几个参数 |
$# | 传递给脚本或函数的参数个数 |
$* | 传递给脚本或函数的所有参数 |
$@ | 传递给脚本或函数的所有参数,被双引号("")包含时,与$*稍有不同 |
$? | 获取上一个操作的结果,0表示正常 |
$$ | 当前Shell进程ID;对于Shell脚本,就是脚本所在进程的ID |
diff
:比较后边文件的内容是否相同test
:用于判断,if test 判断条件
等同于if [判断条件]
这种写法exit 1
:异常退出(0 会正常退出
,1 会异常退出):会给出 Command PhaseScriptExecution failed with a nonzero exit code 的报错SCRIPT_OUTPUT_FILE_0
:下边配置的 Output files 文件中的第0个文件,SCRIPT_OUTPUT_FILE_1就是第1个文件,以此类推- `date "+%Y%m%d-%H%M%S"`:获取中国制式的时间
5.2、USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES
-
当
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
时,表示当Input Files
中的文件有变化时才执行脚本 -
创建了JS脚本后,在下边 Input Files 和 Output Files 中要添加 Shell脚本中要使用的文件
5.3、Shell比对脚本编写
-
使用
diff
命令进行文件比对diff "${SRCROOT}/text.rtf" "${SRCROOT}/text1.rtf" if test $? != 0; then echo "Different Content" >&2 exit 1 fi // ``中包裹要执行的命令 DATE_WITH_TIME=`date "+%Y%m%d-%H%M%S"` echo "The Same Content --- $DATE_WITH_TIME" > ${SCRIPT_OUTPUT_FILE_1}
>&2
:把前面的打印消息重定向到标准错误里面,然而我们很少把其他信息重定向到标准错误中- 一般我们这样写:
"某个命令" 1>/dev/null 2>&1
- 注意 1>/dev/null 和 2>&1 是中间以空格分开的2个独立命令
- 我要执行某个命令,然后因为 1>/dev/null,这个命令执行过程中的所有
标准输出(1)都重定向到 /dev/null空设备
(就是标准输出不打印) - 然后 2>&1 就是
标准错误重定向到标准输出中
,而标准输出前面已经说了不打印,所以 整体就是标准输出、标准错误都不打印
-
扫描 Input Files 文件有变化才会执行脚本,当两个文件内容相同时,产出到 Output Files 中指定的文件中,当文件内容不同时以标准错误输出并异常退出(例中文件相同)
-
Podfile 与 Podfile.lock 文件就是通过这种方式进行比对
6、Pods工程
-
在创建一个Project(不要弄错创建成Target),选择类型Framework
-
将新建的 LZPods.xcodeproj 添加到 workspace 中,需要注意的是不要不小心添加到了project中
Xcode Target Dependencies(Target依赖)
- 当TargetA 要使用 TargetB 的内容,则TargetA 依赖 TargetB
Implicit Dependencies(隐式依赖)
如果 TargetA 和 TargetB 处于同一 project 或者 workspace 下
,则Xcode自动检测依赖关系,Build TargetA 之前会自动 Build TargetB
此时Build LZTarget前会先Build LZPods了
Explicit Dependencies(显示依赖)
在 Build Phases --> Dependencies
中手动添加的依赖,且只有在 同一project下 的Target才能添加
小结
- LZProject 导入 LZPods库(
framework类型的 project
) - LZPods库 下创建 新Target:LZNetWorking(
framework类型的 Target
) - LZPods库 依赖 LZNetWorking
- LZProject运行时会先构建LZPods及其依赖的LZNetWorking
- 如果是
不同project下 添加依赖
可以使用Scheme,因为Scheme 定义了各个 action 应用的Target集合
、以及 要使用的配置以及环境变量等 (每个action可以控制多个Target)
7、Target产物
7.1、产物路径
- 可以 Product --> Show Build Folder in Finder 打开Build产物路径,生成在
DerivedData
文件夹下- workspace名称 / 第一个project名称-复杂字符串
- Build文件夹
Intermediates.noindex
文件夹:存放中间产物Products
文件夹:存放产物
7.2、修改产物路径
- 同一个workspace中,两个project如果target名称相同(比如都叫LZTarget),那么通过Scheme添加依赖同时编译时,编译会报错,原因就是根据 7.1 的产物路径规则,这两个target产出路径会重名,因此我们可以通过修改产物路径,对其多加一层文件夹来做出差异
7.2.1、当前Shell所有环境变量
-
要修改路径,需要用到很多环境变量,可以使用
% printenv
拿到// 获取当前Shell能拿到的所有环境变量 % printenv
-
写到脚本中就能获取Xcode的所有环境变量
-
可以 将环境变量的值 与已有project的路径进行比对,从而确定需要使用哪几个
环境变量名
进行拼接
7.2.2、修改产出路径
-
2个project 分别创建Config文件 修改
CONFIGURATION_BUILD_DIR
为不同路径(CocoaPods自动集成三方库也是增加了文件夹层级,只是因为是Pods所以 环境变量名前多个PODS_
,可以作为参考) -
将2个Config文件分别应用到对应的project中,再运行时即使2个project的target同名也不会报错了