美文网首页ios
Code 是怎么变成 .ipa 包的

Code 是怎么变成 .ipa 包的

作者: 柯浩然 | 来源:发表于2018-05-01 23:57 被阅读91次

    作为一名 iOS 开发工程师,“把代码变成.ipa的可安装到手机上的应用发生了什么”应该是要了解的。提出这么几个问题,去探索一下背后的原理。
    这个过程做了什么?
    程序员可以在这个过程中做什么?

    第一个问题:做了什么
      1. Archive
      1. 打包 .ipa
    Archive
    • 1.进行编译
    • 2.生成一个 DSYM 文件(存储了16进制的函数地址映射)
      奔溃日志中的地址通过此文件由地址映射到具体的函数位置。

    Xcode 的编译器是由 Clang + LLVM 组成

    编译器是做什么的?
    编译器是用来把源代码文件转换为更为低级的语言的(同时还有语句的静态分析),而 xcode 使用的clang 编译器的作用就是把源代码转换为更为低级的 LLVM IR(Intermedia Representation),这个 LLVM IR 是操作系统无关的,然后 LLVM 通过这个中间语言来进行下一步的二进制文件的产出

    • Clang 编译器
      作为编译器前端,作用是:语法分析语义分析生成中间代码.在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。

    通过$ clang -ccc-print-phases <file name>.m观察单个文件的编译过程

    1.预处理
    2.编译生成IR(中间代码)
    语法分析,语义分析(这个过程就是 Compiling)
    3.汇编器生成汇编代码
    4.生成机器码
    5.链接
    6.生成Image,也就是最后的可执行文件

    预处理
    会处理源文件中的宏定义,将代码中的宏用其对应定义的具体内容进行替换,展开头文件:

    #import <Foundation/Foundation.h>
    
    #define MY_CONSTANT 4
    
    #ifdef DEBUG
    //...
    #else
    //...
    #endif
    

    每次 ⌘B 发生的事情(使用cocoa pods)

    • 编译信息写入辅助文件,创建文件架构 .app 文件
    • 处理文件打包信息
    • 执行 CocoaPod 编译前脚本,checkPods Manifest.lock
    • 编译.m文件,使用 CompileC 和 clang 命令
    • 链接需要的 Framework
    • 编译 xib
    • 拷贝 xib ,资源文件
    • 编译 ImageAssets
    • 处理 info.plist
    • 执行 CocoaPod 脚本
      Run custom shell script 'Embed Pods Frameworks'
      Run custom shell script 'Copy Pods Resources'
    • 拷贝标准库
    • 创建 .app 文件和签名
    clang是实际的编译命令
    -x      objective-c 指定了编译的语言
    -arch   x86_64制定了编译的架构,类似还有arm7等
    -fobjc-arc 一些列-f开头的,指定了采用arc等信息。这个也就是为什么你可以对单独的一个.m文件采用非ARC编程。
    -Wno-missing-field-initializers 一系列以-W开头的,指的是编译的警告选项,通过这些你可以定制化编译选项
    -DDEBUG=1 一些列-D开头的,指的是预编译宏,通过这些宏可以实现条件编译
    -iPhoneSimulator10.1.sdk 制定了编译采用的iOS SDK版本
    -I 把编译信息写入指定的辅助文件
    -F 链接所需要的Framework
    -c ClassName.c 编译文件
    -o ClassName.o 编译产物
    

    可以通过 XCode的Link Map File 窥探二进制文件中的布局

    设置为 YES
    • 在此目录下可以找到文件
    ~/Library/Developer/Xcode/DerivedData/<TARGET-NAME>-对应ID/Build/Intermediates/<TARGET-NAME>.build/Debug-iphoneos/<TARGET-NAME>.build/
    

    文件内容包含 Object files,Sections,Symbols

    • Object files

    这个部分的内容都是 .m 文件编译后的 .o 和需要 link 的 .a 文件。前面是文件编号,后面是文件路径。

    • Sections

    这里描述的是每个 Section 在可执行文件中的位置和大小。每个 Section 的 Segment 的类型分为 __TEXT 代码段和 __DATA 数据段两种。

    • Symbols

    Symbols 是对 Sections 进行了再划分。这里会描述所有的 methods,ivar 和字符串,及它们对应的地址,大小,文件编号信息。

    第二个问题:程序员可以做什么

    以 attribute(xx) 的语法格式出现,是 Clang 提供的一些能够让开发者在编译过程中参与一些源码控制的方法。下面列一些会用到的用法:

    attribute((format(NSString, F, A))) 格式化字符串

    - (void)preMethod:( NSString *)string __attribute__((deprecated("preMethod已经被弃用,请使用newMethod")));
    - (void)deprecatedMethod DEPRECATED_ATTRIBUTE; //也可以直接使用DEPRECATED_ATTRIBUTE这个系统定义的宏
    
     //如果没有使用返回值,编译的时候给出警告
    #define __unused_result  __attribute__ ((warn_unused_result)) 
    
    //指定不能有子类
    attribute((objc_subclassing_restricted)) 
    
    //子类继承必须调用 super
    attribute((objc_requires_super)) 
    
    //带描述信息的弃用
    #define __deprecated_msg(_msg) __attribute__((deprecated(_msg)))
    
    //遇到__unavailable的变量/方法,编译器直接抛出Error
    #define __unavailable   __attribute__((unavailable))
    
    //告诉编译器,即使这个变量/方法 没被使用,也不要抛出警告
    #define __unused    __attribute__((unused))
    
    //和__unused相反
    #define __used      __attribute__((used))
    
    //如果不使用方法的返回值,进行警告
    #define __result_use_check __attribute__((__warn_unused_result__))
    
    //OC方法在Swift中不可用
    #define __swift_unavailable(_msg)   __attribute__((__availability__(swift, unavailable, message=_msg)))
    

    clang 警告处理

    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
    ///代码
    #pragma clang diagnostic pop
    

    这段代码的作用是

    对当前编译环境进行压栈
    忽略-Wundeclared-selector(未声明的)Selector警告
    编译代码
    对编译环境进行出栈
    

    通过clang diagnostic push/pop,你可以灵活的控制代码块的编译选项。

    在 Xcode 的 Project editor 中的 Build Setting,Build Phases 和 Build Rules 能够控制编译的过程

    Build Setting

    • 配合多个Target(右键Target,选择Duplicate),单独一个Target负责测试服务器。这样我们就不用每次切换测试服务器都要修改代码了
    • warnings是编码中很重要的一个环节,编译器给出合理的warning能帮助开发者找到自己代码的问题,防止很多bug产生。
      默认用XCode创建一个工程,会自动开启一些重要的warnings,但是更多的时候,我们需要编译器更完整的提醒
      默认的Warning可以在Build Settings里找到

      在search里搜索Warnings,就可以看到如图,这是为所有语言开启的warnings

    当然,也可以为不同语言开启warning,也在Build Settings里
    详情看这里

    Build Phases
    在 Compile Source 中指定所有必须编译的文件,这些文件会根据 Build Setting 和 Build Rules 里的设置来处理
    在 Link Binary With Libraries 里会列出所有的静态库和动态库,它们会和编译生成的目标文件进行链接
    build phase 还会把静态资源拷贝到 bundle 里
    通过右上角的新增,可以添加自己的运行脚本。


    Build Rules
    指定不同文件类型如何编译。每条 build rule 指定了该类型如何处理以及输出在哪。可以增加一条新规则对特定文件类型添加处理方法

    build 过程控制的这些设置都会被保存在工程文件 .pbxproj 里

    提高项目编译速度

    通过$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES可以看到 Xcode 编译时间(首先要关闭 Xcode)

    forward declaration
    选择@class CLASSNAME,而不是#import CLASSNAME.h

    预处理器对 #import 处理是用 CLASSNAME.h 文件中的内容去替换这行代码
     @class 是告诉编译器 CLASSNAME 是一个类,并且在 .m 实现文件中可以通过 import CLASSNAME.h 的方式来使用它。
    

    对常用的工具类进行打包(Framework/.a)
    打包成Framework或者静态库,这样编译的时候这部分代码就不需要重新编译了。

    常用头文件放到预编译文件里
    XCode的pch文件是预编译文件,这里的内容在执行XCode build之前就已经被预编译,并且引入到每一个.m文件里了。

    二. 生成 .ipa
    • Mach-O文件
      是Mac和iOS可以执行文件的格式。进程就是系统根据该格式将执行文件加载到内存后得到的结果。系统通过解析文件建立依赖(动态库),初始化运行时环境,才能真正开始执行该App(进程)

    参考文章:
    https://segmentfault.com/a/1190000003101087
    https://blog.csdn.net/Hello_Hwc/article/details/53557308
    https://blog.csdn.net/forwardto9/article/details/51656274
    https://juejin.im/post/5a352bb0f265da433562d5e3
    https://blog.csdn.net/bjtufang/article/details/50628310
    https://blog.csdn.net/vincentiss/article/details/54617915
    https://objccn.io/issue-6-3/

    相关文章

      网友评论

        本文标题:Code 是怎么变成 .ipa 包的

        本文链接:https://www.haomeiwen.com/subject/otzxrftx.html