【学习总结】06 | App 如何通过注入动态库的方式实现极速编译调试?

1、前言

首先,我认为学习总结,要有所总,所结,就是有归纳后,能用自己的话告诉别人!有所结,就是有所收获输出,一般我认为是思维导图,所以,每篇文章前,我都会先给出文章的脑图:

iOS开发高手课-06-App如何通过注入动态库的方式实现极速编译调试?.png

2、正文

注意,本系列总结不会引用或提供原课程文章所有的内容或代码,只会作出思维导图,需要学习可购买课程 《iOS开发高手课 - 极客时间》

本章是上一节编译器链接器 内容的扩展延伸内容 ----- 动态链接器,所以,如果没有看过上一篇文章的同学,建议先看看:【学习总结】05 | 链接器:符号是怎么绑定到地址上的? | iHTCboy's blog

本章也是沿用之前的风格套路,在讲解 iOS 的动态链接器 如何实现极速编译调试 作用之前,我们先看看程序开发中都有那么经典的例子?如何实现极速编译调试的。首先要看看 2019年最火的 Flutter 吧!所以,我们就先来看看 Flutter 是如何 Hot Reload(热重载)的吧!

Flutter

Flutter-Hot-Reload.gif

如图所示,Flutter 的 热重载(Hot reload)功能可以在无需重新启动应用的情况下快速、轻松地进行测试、构建用户界面、添加功能以及修复错误。

需要注意的是,我们主要是讲解 Flutter 热重载(Hot reload) 相关知识,不会深入讲解 Flutter 其它知识和原理,了解更多可以参考末文扩展学习或自行搜索。

JIT & AOT

Flutter-高性能方案原理.png

Flutter 有两套编译器,JIT,AOT:

  • JIT(Just In Time,即时编译):动态(即时)编译,程序边运行边编译。
  • AOT(Ahead Of Time,运行前编译):指在程序运行前编译。

Flutter DartAOTJIT 两种模式,线上使用时以 AOT 的方式编译成机器代码,保证了线上运行时的效率;而在开发期,Dart 代码可以以 JIT 的方式运行,支持代码的即时生效 热重载(Hot Reload),提高开发效率。

至此,我们应该明白了,Flutter 是通过 JIT 方式实现 Hot Reload(热重载)的!那么,我们接下来,我们就是知道 Flutter 是如何编译代码的,这样才能更加理解 JIT.

Flutter 构建模式

Flutter 支持三种模式编译 app,分别为 debugreleaseprofile

  • Debug模式:对应了Dart的JIT模式,又称检查模式或者慢速模式。支持设备,模拟器(iOS/Android),此模式下打开了断言,包括所有的调试信息,服务扩展和Observatory等调试辅助。此模式为快速开发和运行做了优化,但并未对执行速度,包大小和部署做优化。Debug模式下,编译使用JIT技术,支持广受欢迎的亚秒级有状态的hot reload。
  • Release模式:对应了Dart的AOT模式,此模式目标即为部署到终端用户。只支持真机,不包括模拟器。关闭了所有断言,尽可能多地去掉了调试信息,关闭了所有调试工具。为快速启动,快速执行,包大小做了优化。禁止了所有调试辅助手段,服务扩展。
  • Profile模式:类似Release模式,只是多了对于Profile模式的服务扩展的支持,支持跟踪,以及最小化使用跟踪信息需要的依赖,例如,observatory可以连接上进程。Profile并不支持模拟器的原因在于,模拟器上的诊断并不代表真实的性能。

如果上面很长,那可以快速简要了解三种构建模式(A quick summary for when to use which mode is as follows):

  • 开发过程中,需要使用 热重载 功能,请选择 debug 构建模式;
  • 当你需要分析性能的时候,选择使用 profile 构建模式;
  • 当你需要发布应用的时候,选择使用 release 构建模式。

鉴于 ProfileRelease 在编译原理等上无差异,所以,我们的问题“ Flutter 如何实现 Hot Reload(热重载)的”?就是需要看看 Debug模式 和 Release 模式,当然,前面说到,我们的重点是 JIT,所以重点是要看看 Debug模式 !

Release 模式下的编译

  • iOS
    Flutter-Release模式编译-iOS.png

其中 gen_snapshot 是dart编译器,采用了 tree shaking(类似依赖树逻辑,可生成最小包,也因而在 Flutter 中禁止了dart支持的反射特性)等技术,负责生成汇编形式机器代码。再通过 xcrun 等工具链生成最终的 App.framework。所有的dart代码,包括业务代码,三方package代码,它们所依赖的flutter框架代码,最终将会编译成App.framework
Flutter.framework 对应了Flutter架构中的engine部分,以及Embedder。实际中Flutter.framework位于flutter仓库的/bin/cache/artifacts/engine/ios*下,默认从google仓库拉取。当需要自定义修改的时候,可通过下载engine源码,利用Ninja构建系统来生成。
Flutter相关代码的最终产物是:App.framework(dart代码生成)和 Flutter.framework(引擎)。

  • Android
    Flutter-Release模式编译-Android.png

其中 vm/isolatesnapshotdata/instr 内容均为arm指令,其中vm中涉及runtime等服务(如gc),用于初始化DartVM,调用入口见 DartInitialize(dartapi.h)。isolate则对应了我们的应用dart代码,用于创建一个新的isolate,调用入口见DartCreateIsolate(dart_api.h)。flutter.jar 类似iOS的Flutter.framework,包括了Engine部分(Flutter.jar中的libflutter.so),和Embedder部分(FlutterMain、FlutterView、FlutterNativeView等)。实际中flutter.jar位于flutter仓库的/bin/cache/artifacts/engine/android*下,默认从google仓库拉取。需要自定义修改的时候,可通过下载engine源码,利用Ninja构建系统来生成flutter.jar

本节内容来自:深入理解flutter的编译原理与优化-云栖社区-阿里云

Debug 模式下的编译

Debug模式 下 flutter的编译,结构类似 Release模式,差异主要表现为两点:

  1. Flutter.framework:因为是Debug,此模式下Framework中是有JIT支持的,而在Release模式下并没有JIT部分。(Androidflutter.jariOS一样没有JIT。)
  2. App.framework:不同于AOT模式下的 App.framework 是Dart代码对应的机器代码,JIT模式下,App.framework只有几个简单的API,其Dart代码存在于snapshot_blob.bin文件里(Android的位于flutterassets下的snapshotblob.bin,其它同iOS)。这部分的snapshot是脚本快照,里面是简单的标记化的源代码。所有的注释,空白字符都被移除,常量也被规范化,没有机器码、tree shaking或混淆。

Hot Reload(热重载)

好的,有了上面的基础和概念知识,现在我们开始看看 Flutter 的 Hot Reload(热重载)的执行流程吧!

Flutter-Hot-Reload-执行流程.jpeg

Flutter的热重载是基于State的,也就是我们在代码中经常出现的setState方法,通过这个来修改后,会执行相应的build方法,这就是热重载的基本过程。

Flutter的Hot Reload的实现源码在下面路径中,在此路径中包含run_cold.dartrun_hot.dart两个文件,前者负责冷启动,后者负责热重载

~/flutter/packages/flutter_tools/lib/src/run_hot.dart

上图的流程:当按下R后,Flutter 会扫描修改的文件,并将文件修改前后进行对比,随后会将被改动的代码生成一个kernel files文件。随后会通过HTTP Server将生成的kernel files文件发送给Dart VM虚拟机,虚拟机拿到 kernel文件后会调用_reloadSources函数进行资源重载,将 kernel文件注入正在运行的Dart VM中。当资源重载完成后,会调用RPC接口触发Widgets tree的重建、重绘。

本节以上内容主要来自:移动端开发新趋势Flutter - 简书

调用热重载时,会查看自上次编译以来编辑的代码。重新编译以下文件:

  • 任何有代码更改的文件;
  • 应用程序的主入口文件。
  • 受主入口文件影响的文件。

Dart 2 中,这些文件的 Dart 源代码被转换为 内核文件 并发送到移动设备的 Dart VM。Dart VM 重新加载新内核文件中的所有文件。到目前为止,没有重新执行代码。然后,热重载机制使 Flutter 框架触发所有现有的 widgets 和渲染对象的重建/重新布局/重绘。

以上来自官方文档:热重载 (Hot reload) - Flutter 中文文档

到此,我们就不深入具体的源代码讲解了,大家可以自行查看。这里主要是想讲解整个流程是怎么一回事,了解 Flutter 的 Hot Reload(热重载)是怎么执行流程后,那么 Flutter 能不以实现 热更新代码呢?大家从多角色思考一下,答案是肯定,目前Flutter还在努力中。接下来,我们再来说说热重载的优缺点吧!

不能使用 Hot Reload 的场景

  1. 代码出现编译错误的不能使用 Hot Reload
  2. 代码更改会影响 APP 状态的不能使用 Hot Reload
  3. 全局变量( global variables)和静态字段(static fields)的更改不能使用 Hot Reload
  4. main() 方法里的更改不能使用 Hot Reload
  5. 枚举类型更改为常规的类或者常规的类变为枚举类型也不能使用 HotReload
  6. 修改通用类型声明也不能使用 HotReload

详细可查看官方文档:热重载 (Hot reload) - Flutter 中文文档

React Native

同样的,React Native 也有热加载(Hot Reload)!

React-Native-Hot-Reload-hmr-architecture.png

热加载的基础是模块热替换(HMR,Hot Module Replacement[3]),HMR 最开始是由 Webpack 引入的,我们在 React Native Packager 中也实现了这个功能。HMR 使得 Packager 可以监控文件的改动并发送 HMR 更新消息(HMR update)给包含在 APP 中的一层很薄的 HMR 运行时(HMR runtime)。

简而言之,HMR 更新消息包含 JS 模块中发生变化的代码,当 HMR 运行时接收到这个消息,就使用新的代码替换旧的代码.

以上内容来源:Introducing Hot Reloading · React Native,中文译文:React Native 热加载(Hot Reload)原理简介 - 简书

从这里流程图,大家是不是发现与 Flutter 的流程是一致的,就是具体的技术的实现因平台不同而针对的格式和内容处理。实现热重载(Hot Reload)主流程:

  1. 文件变化:主流程就是监听文件变化,主要是源代码的内容修改。(至于怎么监听,监听那些,怎么做,大家自行研究啦,这里不细说。)
  2. 整合变化:然后把变更的内容打包成框架文件,当然语言和环境不同,文件形式就有差异,这里不详细分析。
  3. 更新变化:最后通过 VM(可以统一理解为虚拟机或Runtime(运行时))进行解析和更新。

通过 FlutterReact NativeHot Reload 流程对比学习后,大家是不是已经理解了热重载的原理流程呢?如果此时,我们再来一个新的平台的热重载功能,你是不是也能猜到大致的流程原理呢?

JVM(Java Virtual Machine,Java虚拟机)

回过头来,我们在看看 JIT。因为,我们本文最终的目的是探究 iOS能否极速编译?的问题,前面所有说到的都是其它平台是如何实现的热重载,所以,JIT 应该是这样叫 JIT Compiler(即时编译器),它是一个编译器。我们需要在深入一点,Java 使用 JIT 编译器进行动态编译已经十几年了!所以,我们以 Java 来例了解一下:

  • Java 平台


    Jave-Platform.jpg
  • Java 编译流程


    Java-Compiler.png

Java 编译的 .class 文件中保存的并不是机器码而只是二进制代码,需要先进行解释,JIT 在运行时会把翻译过的机器码保存起来,以备下次使用。另外,新版本(Java8)的 JVM 默认都是采用混合执行模式,半编译(JIT,热点代码,经常调用循环的代码)、半解释Interpreter,解释执行编译器,一边把字节码翻译成当前硬件平台的机器码一边执行)执行。

不知道大家有没有注意到2个词,JRE(Java Runtime Environment,Java 运行/执行环境)、JVMJava Virtual Machine,Java 虚拟机),JIT Compiler 依赖于 JVMRuntime)中,要实现 Hot Reload,所以是需要一个虚拟机环境。

这种虚拟机和一般其他语言的运行库有一个很大不同,就是它好像一个有独立体系结构的计算机,源代码必须要编译成虚拟机独有的中间语言。虚拟机传统的解释器,就是要在中间语言和真正的平台体系结构之间的指令做映射,比如把Java的load指令换成 native code 的load指令。具体详细的这样就不多说了,主要是想说明,要实现 Hot Reload 用到 JIT 编译器,然后需要依赖于VM虚拟机环境中。

Android VM

既然我们说到了虚拟机环境,那么我们顺便说一下 Android,因为安卓最初也是用 Java 来开发的,当然 Android 应用程序不是使用标准的Java字节码标准的Java虚拟机执行的,所以,这里希望通过对比学习,让大家更加深刻的理解。

Android Build System(安卓构建系统)

Android-Build-System.png

整个构建过程,源代码、资源(resources)、资产(assets)、依赖关系(dependencies)编译(处理)后,封装在成一个 Android 应用程序包(APK)。

深入来说,JVM 是 JAVA 虚拟机,用来运行 JAVA 字节码程序。Dalvik 是 Google 设计的用于 Android 平台的运行时环境,适合移动环境下内存和处理器速度有限的系统。ARTAndroid Runtime(安卓运行时),是 Google 为了替换 Dalvik 设计的新 Android 运行时环境,在 Android 4.4 推出,ART 比 Dalvik 的性能更好,在 Android 5.0 及后续Android版本中, ART 作为正式的运行时库取代了以往的 Dalvik 虚拟机。Android 程序一般使用 Java 语言开发,但是 Dalvik 虚拟机并不支持直接执行 JAVA 字节码,所以会对编译生成的 .class 文件进行翻译、重构、解释、压缩等处理,这个处理过程是由 dx 进行处理,处理完成后生成的产物会以 .dex 结尾,称为 Dex 文件(Dalvik EXecutable),Dex 文件格式是专为 Dalvik 设计的一种压缩格式,为了保证向下兼容,ART 也使用了相同的 Dex 文件。

这里说了那么多,就是为了引出 Dalvik VM (Dalvik 虚拟机)和 ART(Android Runtime,安卓运行时),所以我们就不深入安卓的构建 JACK(Java Android Compiler Kit,Java Android编译器套件) 和 JILL(JACK Intermediate Library Linker,JACK中间库链接器),大家自行搜索了解更多。

Android Runtimes

Android-Dalvik-and-ART-Architectures.png
  • Dalvik VM(Dalvik 虚拟机):采用 JIT技术
  • ART(Android Runtime,安卓运行时):采用 AOT 技术,同时也改善了性能、垃圾回收(Garbage Collection)、应用程序出错以及性能分析等。

无论是 Dalvik VM 还是 Android Runtime,它们在处理 Dex 文件(Dalvik bytecode)上的差异就是性能和存储上,其它并没有什么不同。

Android-Runtimes-Software-Stack.png

这里说说Android Runtime有趣的故事,前面说了采取 Dex 字节代码的应用程序部署到设备时,Android Runtime 将其转换为实际的硬件上运行的机器代码,并且提供了标准的Java库的实现和一些机制。2016 年初,Android N(7.0)把该标准库的实现从 Apache Harmony 改为了OpenJDK。为什么呢?

最初 Java 是 SUN Microsystems公司(太阳计算机系统,简称SUN,太阳公司,名字来源于Stanford University Network(斯坦福大学网络)的缩写。)拥有,并保持开放,OpenJDK 是 SUN 公司为Java平台构建的Java开发环境(JDK)的开源版本,完全自由,开放源码。2010年1月27日,Oracle(甲骨文公司)收购 Sun 之后,Java 易主!由于 Google 没有得到 Sun 公司的授权,所以,这就相当于侵犯了 Oracle 的版权。甲骨文在2010年控告 Google 所开发的 Android 侵犯Java版权,并打算向Google求偿数十亿美元,在这场官司中,地方法院曾一度判决 Google 为合理使用,但上诉法院则认为 Google 的确侵犯Java版权。最终,2016年8月22日,Google 在Android 7.0 Nougat中,将专利的JDK替换成开源方案的OpenJDK,以彻底解决Java的专利问题。

虽然避免未来的侵权纠纷,但是 OpenJDK 是源自于 Java JDK,但它的发展通常比较慢,并不具备所有最新的 Java功能,所以我们的 Android 开发被剥夺了新的语言功能,如streams API(流API)、Lambda, method references(方法引用)等。当然,外界相信在Google 的加持下,OpenJDK 的能力可望突飞猛进。但是,最后 Google 选择了 Kotlin,2017 年的 Google I/O 大会,Google 宣布 Kotlin 成为 Android 开发的一级语言(首选语言),可以说对 Kotlin 寄予了厚望。Kotlin 是一门与 Swift 类似的静态类型 JVM 语言,由 JetBrains 设计开发并开源。与 Java 相比,Kotlin 的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。它与 Java 高度可互操作,可以同时用在一个项目中。了解更多可以查看这篇文章:Java失宠,谷歌宣布Kotlin现在是Android开发的首选语言-InfoQ

OracleGoogle 主要的指控:

  1. Google 在 Android 系统上无偿使用了 37 个 Java APIs,这侵犯了他们的专利,而在 Android 中还有 9 行代码抄袭了 Java,这侵犯了他们的版权。
  2. Android 割裂了 Java生态系统,因为Java的标准字节码文件 .class 文件是不能直接在Android虚拟机上运行的。
  3. Android 通过不当使用 Java API 挤占了 Java ME(Java Platform Micro Edition,Java平台微版) 可能的市场。

那 9 行代码造成抄袭的缘由,据说是因为当时谷歌的一位工程师在为 Android 项目工作的同时,又为 Sun 公司的 OpenJDK 效力,后来,该工程师直接从 OpenJDK 中复制了 9 行代码到 Android 中。所以,大家都要意识到:技术无国界,公司有竞争。技术的风险也是我们技术开发时需要关注的事件。

Dalvik VM(Dalvik 虚拟机)

JIT 最早在 Android 2.2 系统中引进到 Dalvik VM 虚拟机中,Dalvik 虚拟机类似于标准的Java虚拟机,但是它有一些结构上的差异。在应用程序启动时,JIT 通过进行连续的性能分析来优化程序代码的执行,在程序运行的过程中,Dalvik VM 虚拟机在不断的进行将字节码编译成机器码的工作。

ART(Android Runtime,安卓运行时)

ART(Android Runtime,安卓运行时)引入了 AOT 这种预编译技术,在应用程序安装的过程中,ART 就已经将所有的字节码重新编译成了机器码。应用程序运行过程中无需进行实时的编译工作,只需要进行直接调用。因此,ART 极大的提高了应用程序的运行效率,同时也减少了手机的电量消耗,提高了移动设备的续航能力,在垃圾回收等机制上也有了较大的提升。

Dalvik VM 区别是,代替目前在 Runtime 时的 Interpreter(解释执行编译器,一边把字节码翻译成当前硬件平台的机器码一边执行)与 JIT(即时编译器)。具体来说,ART 不是等到App运行的时候才去运行dex文件,而是在App安装的时候就通过 AOT编译器.dex文件编译为对应的.oat二进制文件,当用户点击App的启动图标时,ART直接加载.oat文件去执行。其中那个.oat文件就是一个ELF文件,是当前机器的可执行的文件。

ART 安装及启动App流程

前面说过,ART 是在App安装的时候将.apk文件减压,并将.dex文件预编译为.oat可执行文件,当App启动的时候就不需要在 Runtime 解释执行了,但是这种方式也有它自己的缺点:

  1. 增加了App的安装时间,这个很容易理解,因为多了一个预编译过程。
  2. 增加了App所需要的安装空间,与Dalvik相比,手机上多了一份.oat文件。特别是无论一个App的某一功能是否被使用到以及被使用的频率,例如一个App的某个功能,用户几乎不会去打开,ART都将其编译为.oat文件就显得有点低效了。

Google 的那些天才工程师既然发现了问题,那肯定就会去想办法优化的。技术的每一次进步,都是站在前面技术的肩膀上的,这一次也不例外,Google的工程师将 Interpreter(解释执行编译器,一边把字节码翻译成当前硬件平台的机器码一边执行),JITAOT 三种技术相结合来优化这个过程。

  1. 当你首次安装并一个App的时候,AOT不将.dex文件通过dex2oat编译为.oat文件,这一步减少了安装时间。系统通过Interpreter的方式来启动App,也就是一边把字节码翻译成当前硬件平台的机器码一边执行。
  2. 当一些代码被频繁执行到时,虚拟机就将其编译成机器码,这些被频繁执行的代码有个专有名词,就是hot code(热点代码)。所以,当在App运行过程中探测到了hot code,就使用JIT编译。
  3. 这些通过JIT编译的平台代码及编译配置文件都会被存储在缓存中,加快下次访问的速度。
  4. 当设备处于空闲时间时,AOT 编译器就会启动,结合编译配置文件将热点代码通过dex2oat编译为.oat可执行文件。
  5. 当再次运行App时,ART就可以直接运行.oat文件就可以了。

一款App经过8轮这样的处理基本上就优化好了。Google的工程师没有止步于此,他们仍然在思考,为什么不把编译配置文件共享呢?于是按照这一思路 Google 通过 Google Play 对这一过程做了更高级的优化,当某一款安装了此App的设备处于空闲及WIFI联网情况下时,将其产生的编译配置文件上传到 Google Play上,那么其他用户的此款设备安装此App的时候,Google Play 就知道如何预编译这款App了,这样用户在首次安装的时候,AOT就会完成精准的预编译,那么App的启动及运行就会很流畅。但是很遗憾,原因众所周知,这个优化距离我们很遥远。(对于普通人来说是这样的,同时国内应用不做海外市场的也基本不发布到 Google Play。)

本节内容来源:深入理解Android虚拟机及编译系统

AOT 与 JIT 的优缺点

JIT-AOT.jpeg

图片来源:Thomas Wuerthinger - Twitter

JIT 的优点就不说了,自然是性能好。缺点就是由于 JIT 本质上是一个编译器,所以它必然不是跨平台的,对于每个平台必然需要单独编写代码,当然这个因素一定程度上也导致了 JIT 开发成本高。

AOT 优点是可以提高速度(特别是启动时间)、减少内存足迹(不是JIT)和改进内存回收。缺点是在程序运行前编译会使程序安装的时间增加,将提前编译的内容保存会占用更多的空间等。

最后,AOT 与 JIT 的优缺点,其实没有绝对的,比如 Java 用在服务器可能JIT 更好,因为它并不需要启动很快,而用在客户端移动设备,正如上面说的Android运行时使用:JIT + AOT + Interpreter,不同情况,不同条件,答案也不相同,所以没有最好的,只有最适合的技术方案!

Injection for Xcode

说了那么久,iOS 的同学估计久等啦吧!说了那么多平台,针对 Apple 平台,我们如何实现 极速编译调试 呢?所以,说说 Injection 这个项目是如果实现在 iOS模拟器 中,不必重建或重新启动应用程序,以增量方式更新类的方法的实现热重载,从而为开发人员节省了大量时间。

Xcode-Injection-Hot-Reload.png

前面我们已经说了,实现热重载的三个步骤,1. 文件变化,2. 整合变化,3. 更新变化。这里其实也是一样的,File Watcher监听观察文件改动,然后将改动的文件编译,打包成.dylib动态库文件,然后就是更新这个动态库,这里充当虚拟机的是 Objective-C 的Runtime机制!

大概的流程大家是不是懂了,具体的一些疑问:

  • .dylib动态库为什么可以链接到应用的?
    大家可以在回看上一章的链接器内容 链接器:符号是怎么绑定到地址上的?,动态库是可以在应用运行中,动态加载的,那用什么方法动态加载呢?运行时可以使用dlopen或者Bundle(path: "**.bundle").load()加载动态库。

  • 链接后的新代码,是怎么被执行的?
    把修改后的代码注入了我们的App后,然后通过 Objective-C 的 Runtimeclass_replaceMethod 把整个类的实现方法都替换,然后再调SwiftInjected.injected 我们的类收到消息,开始重绘UI。这里需要注意了,你修改的代码可以没有更新,因为刚才说的我们的类收到消息,但是可能替换的方法,但是界面的生命周期需要重新刷新,更新才会变化。所以,Swift 可以用 @objc func injected()Objective-C- (void)injected 方法,在需要刷新页面或元素时用这方法来主动调用更新 UI。具体示例:

Swift:

extension UIViewController {
    @objc func injected() {
        viewDidLoad()
        viewWillAppear(true)
        viewDidAppear(true)
    }
}

Objective-C:

- (void)injected {
    [self viewDidLoad];
    [self viewWillAppear:YES];
    [self viewWillDisappear:YES];
}

具体详细的原理,大家可以查看这篇文章:Injection:iOS热重载背后的黑魔法

Runtime System(运行时系统)

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

翻译:Objective-C 这门语言会尽可能地将许多决定从编译期推迟到运行时。它会在任何可能的时候动态地处理事情。这意味着这门语言需要的不仅仅是编译器,同时还需要运行时系统来执行编译后的代码。runtime system(运行时系统)好像就是 Objective-C 的操作系统一样,让这门语言能够运作。

以上是 官方文档 上的说明。

iOS-Cocoa-Touch-Architecture.png

刚才说到的 Injection 就是利用了 Objective-C Runtime。用官方原话来说:runtime system(运行时系统)好像就是 Objective-C 的操作系统一样,所以,从上图可知,目前苹果官方的 Cocoa Touch 框架下并没有 JIT 编译器,因此,如果我们要实现动态热加载,可以利用 Objective-CRuntime 机制来实现,但是这个实现是我们自己实现的,所以存在性能不足,还有因为苹果沙盒机制、动态库链接限制等,Injection无法在真机上运行(目前还有很多缺点,可以去官方项目了解。),到此,我们只能期待苹果实现支持啊。等等,那现在问题又来了,Swift 呢?从上图可以看出,Swift API并没有改变现有的 iOS 系统架构,所以,Swift编写的 iOS App 也是没有 JIT的!那么,最后的希望,Swift 能用 Objective-C 的 Runtime 系统吗?

Swift

Swift 能用 Objective-C 的 Runtime 系统吗?

回答这个问题,我们在来看看 官方文档 关于 Rnuntime 的使用说明,我们与 Runtime 进行交互的只能通过下面三种方式:

  1. Objective-C Source Code: 这个最直接,我们写的 OC 代码最终都会被转换为运行时的代码,在程序运行的时候执行的就是这些运行时代码。比如发送、转发消息等等。
  2. NSObject Methods: OC 中,除了 NSProxy 这个类之外,其他所有的类都继承自 NSObject。而 NSObject 中有着类似 class,isKindOfClass,respondsToSelector 这样的方法,他们都是通过 Runtime 来实现的。
  3. Runtime Functions: 在 /usr/include/objc 这个目录中可以找到 Runtime 暴露出来的一些函数和数据结构,通过这些接口我们可以使用一些更高级的操作。比如 Method Swizzle,AssociatedObject 等等。

所以,我们有 Swift 写的 App,能不能使用 Runtime,就是看是否实现了上面的3条原则。这里就不详细展开了,了解更多,可以看看这个文章 Swift和Objective-C的运行时编程 - InfoQ

Xcode Swift Playground

2014年6月2日在 WWDC 2014 大会上宣布并发布了与Xcode集成的macOS版本的 Swift Playgrounds。

  • Everyone Can Code(人人能编程)
    “人人能编程”课程可指导你通过 Swift Playgrounds 来教授编程,这款 app 通过互动式解谜闯关和生动有趣的角色,来引导儿童学习编码。它适合 8 岁以上的学生使用,也能让你用专业 app 开发者使用的语言来介绍编程。

更多官方资料:教育 - 编程教学 - Apple (中国)

课堂指导文档PDF下载:https://www.apple.com/education/docs/everyone-can-code-curriculum-guide.pdf

另外,有人开发了一个在线的Swift PlaygroundOnline Swift Playground,没有 macOS 系统的朋友可以试试。

Swift Playgrounds App

Swift Playgrounds 是适用于 iPad 的一款创新 app,让你能以互动有趣的方式来学习 Swift。它不要求用户具备编程知识,非常适合初学者。许多专业开发者,都是使用 Swift 这一 Apple 创建的编程语言,开发出各款当下热门 app。你可以先从闯关解谜开始,掌握 Swift 的基础知识,再接受一系列的挑战,走向 Apple 和其他顶尖开发者设计的更高级 Playground。

更多官方资料:Swift Playgrounds - Apple (中国大陆)

因为你编写的是真实的代码,所以Swift代码能直接在 Swift Playgrounds App 与 Xcode 之间导入或导出。因此,你可以在专业人士用以开发各款 iOS 和 Mac app 的工具上,来试试自己的创意!

这里 Xcode Swift PlaygroundSwift Playgrounds App 其实是相通了,可以感觉到,苹果的野心不只是为了开发者能实现调试,还有Everyone Can Code(人人能编程),教育行业,应该也是未来的方向,另外,也带动苹果的 iPad 销量,同时也带动了 Swift 语言的流行,我认为,这是多赢的一场漂亮的行动,希望你们也能看到!

SwiftUI

2019年,WWDC2019 上,Apple 推出了一个全新的 SwiftUI 框架,这是一个现代化的 UI 界面编码结构,它是从头开始构建的,以利用 Swift,让开发者感到惊讶。新框架使用声明性范例,让开发者用更少的代码编写相同的 UI。另外,SwiftUI 在 Xcode 中启用实时 UI 编程环境,实时看到编码的页面效果。最令人开发者尖叫的是,实现一次编码,可适应五端 Apple 产品平台。

用苹果的话:编写更少的代码,打造更出色的 app。,具体可以查看苹果官方关于SwiftUI的介绍:Xcode - SwiftUI - Apple Developer

触类旁通

使用类 REPL 模式(Read-Eval-Print-Loop)可以快速运行 Playground 代码,实时看到编码的页面效果。Swift 已经是苹果的未来,也是 iOS 开发者的未来,虽然每年 Swift 从 1.0 到 5.0,有人吐槽学了5种编程语言,但是2020年,Swift 已经稳定,并且开发方向也很多了,Web、服务器、AI、IoT等等,值得大家看看!

另外,教育方向的 可视化编程少儿编程,行业中图形,建筑建模图形化等,我认为也是很大的方向,各家编程语言准备迎战吧!

3、总结

我们文章一开始为了探讨“避免长时间的代码编译过程”的问题,从 热加载JIT 再到 VM,从 FlutterReact NativeJavaAndroidObjective-C 最后到 Swift。大家有没有发现?我们要看到技术的相通,也要看到技术的不同,这里面不同的是什么?学习新技术成本非常高,把技术掌握透了,学习新技术会发现成本很低,很好去理解。

本篇文章跨度比较大,我在团队的分享时,讲很可以比较乱,信息量大,所以,当时与团队分享大家也是没有能很好的理解,所以,现在总结成文章,没有想到文章竟然达到了1万+字数!写文章是真的不容易,这篇文章,春节在家前后几天的时间在研究结构,查资料,希望能让大家都能看明白,因为我们团队每个人的知识和了解水平不同,所以如何写出一篇大家都能懂的技术文章?,而且深浅自如(入门的可以看得懂,深入的可以进一步深入),大家都明白,这就是我的分享态度。当然,文章也可能有错漏的,希望大家回复和讨论交流,分享后,要交流!,看懂了,觉得不错,给个赞吧!谢谢!


注:更多关于 iOS 开发和程序开发相关的内容,可以查看系列,目前还在连载中 【学习总结】iOS开发高手课 -- (连载中) | iHTCboy's blog,以上,希望对你有用!

参考

Flutter


  • 如有侵权,联系必删!
  • 如有不正确的地方,欢迎指导!
  • 如有疑问,欢迎在评论区一起讨论!


注:本文首发于 iHTCboy's blog,如若转载,请注来源


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342