美文网首页iOS分享世界
戴铭(iOS开发课)读书笔记:0506章节-编译提速

戴铭(iOS开发课)读书笔记:0506章节-编译提速

作者: YYYYYY25 | 来源:发表于2019-04-02 14:34 被阅读49次

    原文链接:链接器:符号是怎么绑定到地址上的?
    原文链接:App 如何通过注入动态库的方式实现极速编译调试?


    05 章节 链接器:符号是怎么绑定到地址上的?

    这篇文章主要介绍链接器的相关知识,从底层找答案,解决项目编译速度的问题。
    由于理论知识为主,我只是把相关的知识点做总结梳理,方便自己记忆和学习,如果想要看完整的内容还需阅读原文

    一、iOS 为什么使用编译器?而不是解释器?

    编译器就是将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序。

    一个现代编译器的主要工作流程:源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → 目标代码 (object code) → 链接器(Linker) → 可执行程序 (executables)

    例如 C、C++、Objective-C甚至汇编语言都是上面所提到的高级语言。

    iOS之所以不使用解释器来运行代码,是因为苹果公司希望iPhone的执行效率更高、运行速度更快。

    那么什么是解释器呢?

    解释器(英语:Interpreter),又译为直译器,是一种电脑程序,能够把高级编程语言一行一行直接转译运行。解释器不会一次把整个程序转译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去。

    例如:PythonTCL和各种Shell程序一般而言是使用解释器执行的

    那么,使用编译器和解释器执行代码的特点概括:

    • 采用编译器生成机器码执行的好处是效率高,缺点是调试周期长。
    • 解释器执行的好处是编译调试方便,缺点是执行效率低。
    编译器和解释器对比
    二、iOS开发使用的什么编译器?编译过程如何?

    苹果公司现在使用的编译器是 LLVM(Xcode 5 之前使用的 GCC)。

    编译的主要过程:
    1 LLVM 预处理你的代码,比如把宏嵌入到对应的位置。
    2 预处理后,LLVM 会对代码进行词法分析和语法分析,生成 AST 抽象语法树。
    3 最后 AST 会生成一种更接近机器码的语言 IR。通过 IR 可以生成多份适合不同平台的机器码。对于 iOS 系统,IR 生成的可执行文件就是 Mach-O。

    编译的主要过程
    三、iOS系统的链接器?编译时链接器做了什么?

    LLVM 其实是编译器工具链技术的一个集合。其中的 lld 项目就是内置链接器。

    链接器的作用是完成变量、函数符号和其地址绑定的任务。

    不使用链接器会怎么样?

    不使用链接器的话,首先你就需要在写代码时给每个指令设好内存地址,就好像直接在和不同平台的机器沟通,可读性和可维护性很差。其次这会导致代码和内存地址绑定得太早,也就是说你需要针对不同的平台写多份代码,何必呢?

    链接器为什么要把项目中的多个 Mach-O 文件合并成一个?

    因为单个文件的 Mach-O 文件是无法正常运行的,如果运行时碰到调用在其他文件中实现的函数的情况时,就会找不到这个调用函数的地址,从而无法继续执行。

    最后总结一下链接器对代码主要做了哪几件事
    1 去项目文件里查找目标代码文件里没有定义的变量。
    2 扫描项目中的不同文件,将所有符号定义和引用地址收集起来,并放到全局符号表中。
    3 计算合并后长度及位置,生成同类型的段惊醒合并,建立绑定。
    4 对项目中的不同文件里的变量进行机制地址重定位。
    而且,链接器可以通过打开 Dead code stripping 开关,来开启自动去除无用代码的功能。

    四、动态库链接

    在真实的开发环境中,我们需要用到很多现成的共用库(例如 GUI 框架、网络框架等),这些共用库又分为静态库和动态库两种:

    • 静态库是编译时链接到你的 Mach-O 文件的,无法动态加载和更新。
    • 动态库是运行时链接的库,并没有参与 Mach-O 文件的编译和链接。使用 dyld 可以实现动态加载

    关于 dyld 的具体作用,你可以查看 原文 或者这篇博客:Dynamic Linking On OS X

    简单总结,dyld 做了什么:
    1 先执行 Mach-O 文件,根据 Mach-O 文件里的 undefined 的符号加载对应的动态库,系统会设置一个共享缓存来解决加载的递归依赖问题。
    2 加载后,将 undefined 的符号绑定到动态库里对应的地址上。
    3 最后再处理 +load 方法,main函数返回后运行 static terminator。

    五、最后

    理解了程序从编译、链接、执行、动态库加载到main函数执行的过程,再分阶段的思考和优化。

    编译阶段:每个文件独立编译成 Mach-O 文件等待链接。那么编译器可以根据你修改的文件范围来减少编译,从而提高每次编译的速度。

    链接阶段:文件越多,链接器所需执行的遍历操作就会越多,从而降低编译速度。

    动态库加载阶段:在修改代码之后,尝试不去链接项目中的所有文件,只编译当前修改的文件动态库,通过运行时加载动态库的方式达到及时更新的效果。甚至通过逆向的思维,直接将别人的功能模块作为动态库加载到自己的app中。

    06 章节 App 如何通过注入动态库的方式实现极速编译调试?


    这个章节就是对于上篇文章,在最后的 动态库加载阶段 通过注入动态库的方式实现极速编译。

    原文主要介绍了一款名为 Injection for Xcode 的开源工具的使用和原理。

    开源地址:https://github.com/johnno1962/InjectionIII
    同时我也找到了作者发布的视频演示案例:http://artsy.github.io/blog/2016/03/05/iOS-Code-Injection/

    你可以快速尝试:
    1 在App Store下载InjectionIII。
    2 打开Injectionlll,选择打开项目的根目录。
    3 在项目 AppDelegate 的 applicationDidFinishLaunching(_ application: UIApplication) 加入下面代码:

    #if DEBUG
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
    #endif
    
    Xcode 10.1:
    
    #if DEBUG
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load()
    #endif
    

    4 在需要变更的控制器中添加监听

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.injected(_:)), name: Notification.Name(rawValue: "INJECTION_BUNDLE_NOTIFICATION"), object: nil)
    

    5 实现 injected: 方法,你可以在这里修改代码:

    @objc func injected(_ notification: NSNotification) {
        // to do ...
    }
    

    接下来,你的每次 command + s 都可以触发 injection watching了。

    最后分享一张 Injection 的工作原理图,帮助大家理解。


    这部分的内容太过于底层了,需要大量的时间去消化和理解。先做到了解相关的知识,以后有时间再慢慢消化。

    相关文章

      网友评论

        本文标题:戴铭(iOS开发课)读书笔记:0506章节-编译提速

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