ios

Code 是怎么变成 .ipa 包的

2018-05-01  本文已影响91人  柯浩然

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

第一个问题:做了什么
Archive

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

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

通过$ 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)

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

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

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

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

当然,也可以为不同语言开启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

参考文章:
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/

上一篇下一篇

猜你喜欢

热点阅读