Chris Lattner访谈录
1,LLVM(Low Level Virtual Machine):
先说说什么是编译器:编译器就是把程序员的代码翻译成机器可以理解的语言的工具。
再谈谈LLVM:一个模块化和可重用的编译器和工具链接技术的集合,clang是LLVM的子项目,是c,c++,oc编译器,因为多模块的复用,所以提供了惊人的快速编译,比gcc快3倍。其中的 clang static analyzer 主要是进行语法分析,语义分析和生成中间代码,当然这个过程会对代码进行检查,出错的和需要警告的会标注出来。LLVM 核心库提供一个优化器,对流行的 CPU 做代码生成支持。lld 是 Clang / LLVM 的内置链接器,clang 必须调用链接器来产生可执行文件。LLVM 比较有特色的一点是它能提供一种代码编写良好的中间表示 IR,这意味着它可以作为多种语言的后端,这样就能够提供语言无关的优化同时还能够方便的针对多种 CPU 的代码生成。
2,LLVM三层结构:
第一层:clang编译器,负责编译各种语言;第二层:代码优化器,通过模块化操作优化代码,是bitcode逻辑的主要部分;第三层:代码翻译器,针对不同平台和GPU将代码翻译成机器语言
3,LLDB:一个有着repl的特性和c++,python插件的开源调试器,LLDB绑定在xcode内部,存在与主窗口底部的控制台中
4,libc++,libc++ ABI:高性能c++标准库实现,支持c++ 11
5,compiler -rt:为LLVM和clang设计的编译器扩展函数库。针对__fixunsdfdi和其他目标机器上没有一个核心IR(intermediate representation)对应的短原生指令序列时,提供高度调优过的底层代码生成支持。
6, ABI (Application Binary Interface),中文名:应用二进制接口,是app和操作系统、其他应用之间的二进制接口。它包括以下细节:数据类型的大小、布局和对齐;调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递还是部分参数通过寄存器传递,哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;系统调用的编码和一个应用如何向操作系统进行系统调用;以及在一个完整的操作系统abi中,目标文件的二进制格式,程序库等等。
7,在xcode的project editor中的Build Setting,Build Phases和Build Rules能够控制编译的过程。其中,
Build Phases:构建可执行文件的规则,指定target的依赖项目,在target build之前需要先build的依赖,在compile source 中指定所有必须编译的文件,这些文件会根据building setting build rules里的设置来处理。在Link Binary With Libraries里会列出所有的静态库和动态库,它们会和编译生成的目标文件进行链接。build phases 还会把静态资源拷贝到bundle里。还可以通过在build phases里添加自定义脚本来做些事情,比如像 CocoaPods 所做的那样。
Build Rules:指定不同文件类型如何编译。每条 build rule 指定了该类型如何处理以及输出在哪。可以增加一条新规则对特定文件类型添加处理方法。
Build Settings:在 build 的过程中各个阶段的选项的设置。
8,静态分析( clang static analyzer):词法->语法->语义->IR->优化->CodeGen
9,IR 整体结构:一个编译的单元即一个文件在 IR 里就是一个 Module,最大的是 Module,里面包含多个 Function,每个 Function 包含多个 BasicBlock,BasicBlock 里含有 Instruction,代码非常清晰,这样如果想开发一个新语言只需要完成语法解析后通过 LLVM 提供的丰富接口在内存中生成 IR 就可以直接运行在各个不同的平台。IR 语言满足静态单赋值,可以很好的降低数据流分析和控制流分析的复杂度。及只能在定义时赋值,后面不能更改。但是这样就没法写程序了,输入输出都没法弄,所以函数式编程才会有类似 Monad 这样机制的原因。
10,dSYM 文件:在每次编译后都会生成一个 dSYM 文件,程序在执行中通过地址来调用方法函数,而 dSYM 文件里存储了函数地址映射,这样调用栈里的地址可以通过 dSYM 这个映射表能够获得具体函数的位置。一般都会用来处理 crash 时获取到的调用栈 .crash 文件将其符号化。可以通过 Xcode 进行符号化,将 .crash 文件,.dSYM 和 .app 文件放到同一个目录下,打开 Xcode 的 Window 菜单下的 organizer,再点击 Device tab,最后选中左边的 Device Logs。选择 import 将 .crash 文件导入就可以看到 crash 的详细 log 了。还可以通过命令行工具 symbolicatecrash 来手动符号化 crash log。同样先将 .crash 文件,.dSYM 和 .app 文件放到同一个目录下,然后输入下面的命令:export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer symbolicatecrash appName.crash appName.app > appName.log
11,Mach-O 文件:记录编译后的可执行文件,对象代码,共享库,动态加载代码和内存转储的文件格式。不同于 xml 这样的文件,它只是二进制字节流,里面有不同的包含元信息的数据块,比如字节顺序,cpu 类型,块大小等。文件内容是不可以修改的,因为在 .app 目录中有个 _CodeSignature 的目录,里面包含了程序代码的签名,这个签名的作用就是保证签名后 .app 里的文件,包括资源文件,Mach-O 文件都不能够更改。
Mach-O 文件包含三个区域:
Mach-O Header:包含字节顺序,magic,cpu 类型,加载指令的数量等
Load Commands:包含很多内容的表,包括区域的位置,符号表,动态符号表等。每个加载指令包含一个元信息,比如指令类型,名称,在二进制中的位置等。
Data:最大的部分,包含了代码,数据,比如符号表,动态符号表等。
12,dyld动态链接:生成可执行文件后就是在启动时进行动态链接了,进行符号和地址的绑定。首先会加载所依赖的 dylibs,修正地址偏移,因为 iOS 会用 ASLR 来做地址偏移避免攻击,确定 Non-Lazy Pointer 地址进行符号地址绑定,加载所有类,最后执行 load 方法和 clang attribute 的 constructor 修饰函数。
13,dylib 这种格式的表示是动态链接的,编译的时候不会被编译到执行文件中,在程序执行的时候才 link,这样就不用算到包的大小里,而且也能够不更新执行程序就能够更新库。
HTTP 2.0的那些事
1,HTTP站在TCP之上:HTTP是建立在TCP协议之上的,TCP协议作为传输层协议其实离应用层并不远。HTTP协议的瓶颈及其优化技巧都是基于tcp协议本身的特性,比如tcp建立链接时三次握手有1.5个RTT (round-trip time)的延迟,为了避免每次请求都经历握手带来的延迟,应用层会选择不同策略的http长链接方案,又比如tcp在建立连接时的初期有慢启动,(slow start)的特性,所以连接的重用总比新建连接性能要好。
2,影响一个网络请求有2个因素:带宽和延迟。随着基础建设的完善,带宽已经有了很大的改善,所以现在延迟就变成了影响网络请求最主要的原因。
3,客户端是依据域名来向服务器建立连接的,一般pc端浏览器会针对单个域名的server同时建立6-8个连接,而手机端则控制在4-6个,显然连接数并不是越多越好,因为连接数越多带来的资源开销和整体延迟都会变大。
4,http1.0的主要面临的问题有2个:第一就是连接无法复用,这样就导致每次请求都会经历三次握手和慢启动,三次握手在高延迟的场景下更明显,如果某次握手过程中,某一次发生了延迟,这就导致整个过程都会延迟,慢启动则对文件类请求影响较大;第二就是head of line blocking,这个会导致带宽无法充分被利用,以及后续健康请求被阻塞。多个请求一起发生时,请求也是一个接着一个进行的,相当于串行队列,后面的需要等到前面的返回了才开始,如果一个有延迟或者不回来,后面的就都有问题了。
5,针对http1.0的连接无法复用的问题,有以下四种方案:(仅app端)
a:基于tcp的长连接,其实就是基于socket的编程,自己制定协议。
b:http long-polling,就是开始就发出连接,但是不马上返回,等到有新数据才返回,始终有一个连接保持着,返回后用马上发出新的连接,如此往复。(单向)
c:http streaming,跟b类似,不过初始的streaming不会结束,一直通过这个通道返回数据。(单向)
d:web socket,基于tcp协议,提供双向数据通道,且提供了message的概念,比基于字节流的tcp socket更简单,而且也有长连接功能,不过比较新,有些不支持。
6,head of line blocking:是http2.0之前网络体验的最大祸源,健康的请求会被不健康的请求影响,而且这种体验的损耗受网络环境的影响,出现随机而难以控制。为了解决延迟这个问题,协议的设计者们设计了一种全新的pipelining,但是其实也没有什么卵用,区别把几个请求一次发送过去,不过回来的时候还是要等的,本质上还是没有解决问题。
7,导致网络请求延迟的有:tcp初期的slow start,tcp的三次握手,dns查询的延迟等。
8,http2.0是以SPDY为原型进行讨论和标准化的。SPDY基础功能包括:多路复用,请求优先级,header压缩;高级功能包括:server高级推送,server暗示。
9,HTTP2.0针对HTTP1.x的主要改动如下:
1,新的二进制格式:HTTP1.x使用的是明文协议,而HTTP2.0使用的是binary格式
2,连接共享:多路复用,同时还可以设置优先级和依赖
3,header压缩:高效的压缩算法很大的压缩header,减少发送包的数量从而降低延迟
4,HPACK压缩算法的使用
5,重置连接表现更好
6,server push
7,用类似tcp的receive window做流量控制
8,更安全的ssl
10,iOS http现状:iOS系统是从iOS8开始通过NSURLSession来支持SPDY的,iOS9+开始自动支持http2.0;怎么配置最佳的http方案这个因app而异,一是app本身http流量是否大而且密集,二是团队本身的技术条件。http2.0的部署相对容易,客户端开发者甚至不用做什么改动,只需要使用iOS9的sdk编译即可,但是缺点是http2.0只适用于iOS9的设备;SPDY的部署相对麻烦一些,不过可以兼容iOS6+的设备,所以这个具体自己看吧。
Auto Layout :Snapkit源码剖析
1,Snapkit是目前Swift中通过代码进行Auto Layout布局时最流行的开源库。与OC中最主流的Auto Layout开源库Masonry是同一个团队维护,有着相似的API风格。
2,Snapkit由于是swift写的,我以前看过,不过很久没有再看,项目也没有用到,所以这个源码解析我基本没看懂,哈哈。
3,fastlane是一套自动化打包的工具集,用ruby写的,用于ios和Android的自动化打包和发布等工作。
CFArray的历史渊源以及实现原理
1,在ObjC的初期(2011年之前),CFArray是使用deque双端队列实现的,所以会呈现出头尾操作高效,而中间操作成线性的特点,在容量超过300000左右时,时间复杂度发生陡变。当数据超出阀值的时候,会将数据结构从CFArray转换成CFStorage,CFStorage是一个平衡二叉树结构,为了维护数组的顺序访问,将node的权值使用下标完成插入和旋转操作。不过在2011年以后,CFArray取消了数据结构转换这一功能,或许是为了防止大数据时候二叉树建树的时间抖动问题从而取消的这个特性。其实从它数据结构描述看来,CFArray就是由单一的双端队列进行实现,而且还记录了一些容量信息。
2,C中数组最显著的缺点就是在下标0处插入时,需要移动所有的元素,类似的,当删除第一个元素,在第一个元素前插入一个元素也会造成O(n)复杂度的操作,然而数组是常读写容器,所以O(n)的操作会造成很严重的时间开销。
load 方法的调用时机
1,dyld(The Dynamic Link Editor)是苹果的动态链接库,系统内核做好启动程序的初始准备后,将其他事务交给dyld负责。
2,镜像(images)表示的是二进制文件(可执行文件或者动态链接库的.so文件)编译后的符号,代码等。
3,+ load 方法:作为 Objective-C 中的一个方法,与其它方法有很大的不同。它只是一个在整个文件被加载到运行时,在 main 函数调用之前被 ObjC 运行时调用的钩子方法。关键字有那么几个:文件刚加载,main 函数之前,钩子方法;
4,load 方法是如何被调用的?当 Objective-C 运行时初始化的时候,会通过 dyld_register_image_state_change_handler 在每次有新的镜像加入运行时的时候,进行回调。执行 load_images 将所有包含 load 方法的文件加入列表 loadable_classes ,然后从这个列表中找到对应的 load 方法的实现,调用 load 方法。
5,对于 load 方法的调用顺序有两条规则:父类先于子类调用,类先于分类调用;为什么会这样呢,那是因为如果你看过源码你就会发现在把它们添加进可以加载的列表之前就把顺序定好了,比如加进列表的时候会先添加父类再添加子类,先添加类,再添加分类,这样就能确保调用的顺序是正确的。
6,load 的应用:load 可以说我们在日常开发中可以接触到的调用时间最靠前的方法,在主函数运行之前,load 方法就会调用。由于它的调用不是惰性的,且其只会在程序调用期间调用一次,最最重要的是,如果在类与分类中都实现了 load 方法,它们都会被调用,不像其它的在分类中实现的方法会被覆盖,这就使 load 方法成为了方法调剂(Method swizzling)的绝佳时机。但是由于 load 方法的运行时间过早,所以这里可能不是一个理想的环境,因为某些类可能需要在在其它类之前加载,但是这是我们无法保证的。不过在这个时间点,所有的 framework 都已经加载到了运行时中,所以调用 framework 中的方法都是安全的。
7,Load 方法作用:load方法是我们在开发中最接近app启动的可控方法,即在app启动以后,入口函数main之前,由于调用有着non-lazy属性,并且运行期只调用一次,于是我们可以使用load独有的特性和调用时机来尝试Method Swizzling,当然因为load调用时机过早,并且当多个class没有关联(继承与派生),我们无法知道class中load方法的优先调用关系,所以一般不会在load方法中引入其他的类,这是在开发中要注意的。