Xcode工程管理

717 阅读10分钟

1、Xcode整体结构

image.png

  • WorkSpace:工作空间,项目Project 在这个空间中工作(可以多Project

  • Target对指定代码和资源文件的具体构建方式一个Target对应生产一个产物(iPhone、iPad产物等) image.png

  • Configuration:产物配置,项目Project需要通过Config管理Target image.png

  • Scheme对指定Target的环境配置

小结

  1. Xcode在WorkSpace工作区中 可以有多个Project项目
  2. 每个Project通过 Configuration配置 可生成 一个对应的Target产物

2、WorkSpace 和 Project

  • 我们 所有的项目工程都是通过 WorkSpace 进行管理,即使明面上没有 .xcworkspace文件 的项目,我们只需 显示包内容 就能看到 image.png image.png

2.1、生成WorkSpace

image.png image.png image.png

  • contents.xcworkspacedata:workspace的配置信息,XML格式,包含Project的路径等内容
  • xcshareddata:Scheme分享给别人看的配置(Product --> Scheme --> Manage Schemes
  • xcuserdata:私有的配置 image.png

2.2、生成Project并与WorkSpace关联

  1. 我们新建一个空project,路径与上边workspace文件相同 image.png

  2. 此时打开worksapce还是没有project,因为没有将两者进行关联;我们 在workspace 中将 project 进行添加 image.png

  3. 添加好后再打开workspace的 contents.xcworkspacedata 文件可以看到project路径被添加进来了(以workspace文件为基准路径image.png

    self:      -> 在.xcworkspace所在目录下有同名,并以.pbxproj结尾的文件
    group:     -> 指定目录下.xcodeproj结尾文件的路径
    container: -> 在.xcworkspace当前目录下有不同名,但以.xcodeproj结尾文件
    absolute:  -> 绝对路径
    workspace中的所有project都构建在同一个目录中
    

2.3、Project中添加Target

  1. 光有空的project并不能完成代码编写,还是空荡荡的,此时我们要新建target image.png image.png

  2. 建好了target,项目中就有我们熟悉的代码编写的 AppDelegateViewControllerBuild settingBuild Phases 这些东西了 image.png

2.4、Configuration产出环境

  1. 有了target我们可以进行产物产出,但我们知道target产出时需要配合Configuration 的不同产出环境,默认是有DebugRelease两种,可我们也可以对其进行增加环境,来适应更多的环境配置
    image.png

  2. 添加后我们在Build Setting等地方配置产出环境时就不止 Debug 和 Release 两种image.png

3、Target 和 Scheme

  • Scheme 定义了各个 action 应用的Target集合、以及 要使用的配置以及环境变量等
  1. Scheme 拥有 多种 ActionBuildAnalyzeArchiveExecutionLaunchProfileTestimage.png

  2. 每个Action 可以展开,我们可以在 执行每个Action前与后 插入脚本发送邮件 image.png image.png

  3. 我们可以新建Scheme,并指定其运行环境为Release,这样 同一个Target只要在上边切换Scheme就可简单的运行在不同的配置环境中 image.png image.png

  4. Scheme 存储在project包中,在哪个文件夹取决于是否设置为share
    image.png 因为演示工程的Scheme都是share的,所以我们能在 xcsharedata 文件夹中看到两个Scheme的文件

  5. 终端直接生成产物命令

    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

  1. 自定义Beta产出环境Configuration image.png

  2. 建立个用于Beta环境的Scheme image.png

  3. 配置该Scheme的产出环境为Beta image.png

  4. User-Defined中添加自定义的 Host_Url字段并根据不同的产出环境设置不同的url

    • 我们可以看到,User-Defined中这些已有内容是下文 4.4 部分创建的用于修改环境变量的.xcconfig文件配置后自动产生的,config文件中写入环境变量User-Defined就跟着新增、改变,这里提前提一下

    image.png

  5. 在info.plist文件中增加对应的HOST_URL字段,并 取HOST_Url的值(各环境的AppIcon、app名称、bundle identifier也都是可以配置的) image.png

  6. 在需要的地方通过plist文件进行使用

    NSString *path = [NSBundle.mainBundle pathForResource:@"Info" ofType:@"plist"];
    NSDictionary *infoDic = [[NSDictionary alloc] initWithContentsOfFile:path];
    NSLog(@"%@",infoDic[@"HOST_Url"]);
    

3.2、多Target配置多环境

通过多Target对不同环境设置不同的值

  1. 复制Target,在Target项与Scheme中对其改名
  2. Preprocessor Macros中对不同环境添加 同名的宏(图中宏命名为Development)
    • 对原Target,这个宏在Debug模式下设置为0,Release模式下设置为1 image.png
    • 对copy的Target,这个宏,在Debug模式下设置为2,Release模式下设置为3 image.png
  3. 调整2个Target的Scheme运行的环境 image.png
  4. 在适当的地方使用,例如:
    - (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;
    }
    
  5. 创建给不同Target用的AppIcon,再在不同Target中进行设置 image.png image.png
  6. 这样,就可以根据多个Target在对应Scheme中配置的运行环境不同,而得到不同的宏值
注意:

小结

  • 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、产物产出路径

image.png image.png

4.2、环境变量

  • 环境变量会使用关键字Export修饰,以便外界使用
  1. Xcode会将 Build Settings 中的编译配置导出,转换成环境变量(各种配置项在右侧栏的?选项中能找到 对应的环境变量名image.png

  2. 验证打印环境变量(使用JS输出环境变量) image.png

    • 能看到打印的内容与自己做的标记:"------LZTargetJS" image.png
    • 如果展开看详情可以看到大量export:环境变量,这都是根据Xcode中的各种配置转换生成的 image.png
重要:Xcode内置常用环境变量
  • $(PROJECT_DIR):代表的是整个项目
  • ${SRCROOT}:表示当前工程所在目录 image.png
  • ${PODS_ROOT}:代表的是pod目录
  • ${BUILD_DIR}:Products目录
  • ${LOAD_PATH}:project使用的所有三方库的路径
  • $(inherited):继承上一级或依赖项的配置,让当前变量继承变量原有值
    • 通过CocoaPods集成的项目,$(inherited) 将会包含 Pods.xcodeproj 中的配置
    • target在设置自己路径的时候如果加了这个,那么就是 继承project里设置的路径,如果不需要继承就不加,要不然乱加有可能整混导致路径错误

recursive(会在相应的目录递归搜索文件)
non-recursive (非递归)

4.3、跨终端Shell输出

  1. 每个终端都有其唯一的标识符,可以通过终端命令% tty,来获取当前终端标识符,通过该标识符可以进行跨终端操作 image.png

  2. 将脚本输出内容重定位到已打开的终端上(坑:不点开终端运行会报错...)
    image.png

    • 1>/dev/ttys000
      • 1:表示正确
      • >:表示进行重定位
      • /dev/ttys000:已打开的终端唯一标识符
    • 原本应该打印在Xcode上的内容转为打印到终端窗口上

4.4、修改环境变量

  • .xcconfig:专门管理和配置Xcode环境变量的文件
  1. 新建Configuration Settings文件 image.png

  2. Config文件 中对环境变量进行配置(可通过include关键字导入其他Config内的配置:#include "Debug.xcconfig"image.png

  3. 此时在 Build Settings 中并不能看到有什么改变,因为在一开始的图中我们就看到,Configuration 是 Project 中用于配置 Target 的,因此 我们需要在 Project 中将新建的 Config 文件应用进去(例中只对Project的Debug环境进行应用Config文件) image.png

  4. 完成上面的步骤,我们就能看到 Build Settings 中的对应项被修改为我们Config文件中的内容 image.png

  5. Build Settings 中手动配置的优先级高于 Config文件 image.png 可以使用$(inherited)继承使 Config文件 生效 image.png

  6. 还可以进行条件化配置(例子中设置只在Debug模式下) image.png

4.5、自定义环境变量

  1. 在Config文件中可以自定义环境变量,要注意// 会当做注释符所以要做处理(引号也会被保留,不需要引号就不用添加) image.png

  2. 在脚本中输出测试一下自定义的环境变量 image.png

  3. 此时虽然能在脚本中调用,但是并不能在代码中使用,如果想要使用可以配置在Info.plist中,然后在 代码中将 info.plist 转成字典后再使用 image.png 上图我们可以看到,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、创建命令

  1. 编写命令输出 Mach-O 文件路径 image.png

  2. 也是通过${变量}来执行命令 image.png

5、Shell脚本比对文件内容

5.1、Shell常用命令介绍

5.1.1、基础符号
符号作用
>重定向
&引用
0stdin 标准输入
1stdout 标准输出
2stderr 标准错误
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 的报错 image.png
  • SCRIPT_OUTPUT_FILE_0:下边配置的 Output files 文件中的第0个文件SCRIPT_OUTPUT_FILE_1就是第1个文件,以此类推 image.png
  • `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中的文件有变化时才执行脚本 image.png

  • 创建了JS脚本后,在下边 Input FilesOutput Files 中要添加 Shell脚本中要使用的文件 image.png

5.3、Shell比对脚本编写

  1. 使用diff命令进行文件比对 image.png

    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/null2>&1 是中间以空格分开的2个独立命令
      1. 我要执行某个命令,然后因为 1>/dev/null,这个命令执行过程中的所有标准输出(1)都重定向到 /dev/null空设备就是标准输出不打印
      2. 然后 2>&1 就是标准错误重定向到标准输出中,而标准输出前面已经说了不打印,所以 整体就是标准输出、标准错误都不打印
  2. 扫描 Input Files 文件有变化才会执行脚本,当两个文件内容相同时,产出到 Output Files 中指定的文件中,当文件内容不同时以标准错误输出并异常退出(例中文件相同) image.png

  3. PodfilePodfile.lock 文件就是通过这种方式进行比对

6、Pods工程

  1. 在创建一个Project(不要弄错创建成Target),选择类型Framework image.png image.png

  2. 将新建的 LZPods.xcodeproj 添加到 workspace 中,需要注意的是不要不小心添加到了project中 image.png

Xcode Target Dependencies(Target依赖)

  • 当TargetA 要使用 TargetB 的内容,则TargetA 依赖 TargetB
Implicit Dependencies(隐式依赖)

如果 TargetA 和 TargetB 处于同一 project 或者 workspace 下则Xcode自动检测依赖关系,Build TargetA 之前会自动 Build TargetB

image.png 此时Build LZTarget前会先Build LZPods了 image.png

Explicit Dependencies(显示依赖)

Build Phases --> Dependencies 中手动添加的依赖,且只有在 同一project下 的Target才能添加

image.png

小结

  1. LZProject 导入 LZPods库(framework类型的 project
  2. LZPods库 下创建 新Target:LZNetWorkingframework类型的 Target
  3. LZPods库 依赖 LZNetWorking
  4. LZProject运行时会先构建LZPods及其依赖的LZNetWorking
  5. 如果是不同project下 添加依赖可以使用Scheme,因为Scheme 定义了各个 action 应用的Target集合、以及 要使用的配置以及环境变量等 (每个action可以控制多个Target) image.png

7、Target产物

7.1、产物路径

  • 可以 Product --> Show Build Folder in Finder 打开Build产物路径,生成在 DerivedData 文件夹下 image.png image.png
    1. workspace名称 / 第一个project名称-复杂字符串
    2. Build文件夹
    3. Intermediates.noindex文件夹:存放中间产物
    4. Products文件夹:存放产物

7.2、修改产物路径

  • 同一个workspace中,两个project如果target名称相同(比如都叫LZTarget),那么通过Scheme添加依赖同时编译时,编译会报错,原因就是根据 7.1 的产物路径规则,这两个target产出路径会重名,因此我们可以通过修改产物路径,对其多加一层文件夹来做出差异
7.2.1、当前Shell所有环境变量
  1. 要修改路径,需要用到很多环境变量,可以使用% printenv拿到

    // 获取当前Shell能拿到的所有环境变量
    % printenv
    

    image.png

  2. 写到脚本中就能获取Xcode的所有环境变量 image.png

  3. 可以 将环境变量的值 与已有project的路径进行比对从而确定需要使用哪几个环境变量名进行拼接

7.2.2、修改产出路径
  1. 2个project 分别创建Config文件 修改CONFIGURATION_BUILD_DIR为不同路径(CocoaPods自动集成三方库也是增加了文件夹层级,只是因为是Pods所以 环境变量名前多个PODS_,可以作为参考) image.png

  2. 将2个Config文件分别应用到对应的project中,再运行时即使2个project的target同名也不会报错了