介绍
最近打算重新学习一下 Swift,因为之前一直使用 objc 的思维模式写 Swift,另外想进一步了解 Swift 语言的设计实现,应该能丰富我的认知。 特意找了个旧设备装上 macOS 10.14.6 系统,并安装上 Xcode 11 ,新版本语法特性太多,暂时先不考虑,只从早期 ABI 稳定又稍微升级过的 5.1 版本开始。
swift -version
# Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
# Target: x86_64-apple-darwin18.7.0
Swift 编译流程
开始之前,先了解下 Swift 语言的编译流程
图中命令其实是独立的,这样画只是说明它经历过哪些环节,先是从 Swift 代码开始,编译器会对代码进行分词,解析,生成抽象语法树,这期间会进行语义分析,譬如类型检查之类的操作。
接着就是把生成的 ast
转成 sil
,既然有 sil
,那自然就是优化 sil
,完了就生成 LLVM 的 IR 码,然后通过 LLVM 把得到的 IR 码转成机器代码(期间会进行编译,链接库等操作,然后才生成对应平台的二进制文件)。
Swift 基本命令
swiftc
这个是编译器的命令,可以通过 swiftc --help
来查看有哪些参数。
OVERVIEW: Swift compiler
USAGE: swiftc
MODES:
-dump-ast Parse and type-check input file(s) and dump AST(s)
-dump-parse Parse input file(s) and dump AST(s)
-dump-scope-maps <expanded-or-list-of-line:column>
Parse and type-check input file(s) and dump the scope map(s)
-dump-type-info Output YAML dump of fixed-size types from all imported modules
-dump-type-refinement-contexts
Type-check input file(s) and dump type refinement contexts(s)
-emit-assembly Emit assembly file(s) (-S)
-emit-bc Emit LLVM BC file(s)
-emit-executable Emit a linked executable
-emit-imported-modules Emit a list of the imported modules
-emit-ir Emit LLVM IR file(s)
-emit-library Emit a linked library
-emit-object Emit object file(s) (-c)
-emit-sibgen Emit serialized AST + raw SIL file(s)
-emit-sib Emit serialized AST + canonical SIL file(s)
-emit-silgen Emit raw SIL file(s)
-emit-sil Emit canonical SIL file(s)
-index-file Produce index data for a source file
-parse Parse input file(s)
-print-ast Parse and type-check input file(s) and pretty print AST(s)
-resolve-imports Parse and resolve imports in input file(s)
-typecheck Parse and type-check input file(s)
OPTIONS:
-api-diff-data-dir <path>
Load platform and version specific API migration data files from <path>. Ignored if -api-diff-data-file is specified.
-api-diff-data-file <path>
API migration data is from <path>
-application-extension Restrict code to those available for App Extensions
-assert-config <value> Specify the assert_configuration replacement. Possible values are Debug, Release, Unchecked, DisableReplacement.
-continue-building-after-errors
Continue building, even after errors are encountered
-debug-info-format=<value>
Specify the debug info format type to either 'dwarf' or 'codeview'
-debug-info-store-invocation
Emit the compiler invocation in the debug info.
-debug-prefix-map <value>
Remap source paths in debug info
-disable-autolinking-runtime-compatibility-dynamic-replacements
Do not use autolinking for the dynamic replacement runtime compatibility library
-disable-autolinking-runtime-compatibility
Do not use autolinking for runtime compatibility libraries
-disable-migrator-fixits
Disable the Migrator phase which automatically applies fix-its
-driver-time-compilation
Prints the total time it took to execute all compilation tasks
-dump-migration-states-dir <path>
Dump the input text, output text, and states for migration to <path>
-dump-usr Dump USR for each declaration reference
-D <value> Marks a conditional compilation flag as true
-embed-bitcode-marker Embed placeholder LLVM IR data as a marker
-embed-bitcode Embed LLVM IR bitcode as data
-emit-dependencies Emit basic Make-compatible dependencies files
-emit-loaded-module-trace-path <path>
Emit the loaded module trace JSON to <path>
-emit-loaded-module-trace
Emit a JSON file containing information about what modules were loaded
-emit-module-interface-path <path>
Output module interface file to <path>
-emit-module-interface Output module interface file
-emit-module-path <path>
Emit an importable module to <path>
-emit-module Emit an importable module
-emit-objc-header-path <path>
Emit an Objective-C header file to <path>
-emit-objc-header Emit an Objective-C header file
-emit-tbd-path <path> Emit the TBD file to <path>
-emit-tbd Emit a TBD file
-enable-library-evolution
Build the module to allow binary-compatible library evolution
-enforce-exclusivity=<enforcement>
Enforce law of exclusivity
-fixit-all Apply all fixits from diagnostics without any filtering
-framework <value> Specifies a framework which should be linked against
-Fsystem <value> Add directory to system framework search path
-F <value> Add directory to framework search path
-gdwarf-types Emit full DWARF type info.
-gline-tables-only Emit minimal debug info for backtraces only
-gnone Don't emit debug info
-g Emit debug info. This is the preferred setting for debugging with LLDB.
-help Display available options
-import-underlying-module
Implicitly imports the Objective-C half of a module
-index-file-path <path> Produce index data for file <path>
-index-ignore-system-modules
Avoid indexing system modules
-index-store-path <path>
Store indexing data to <path>
-I <value> Add directory to the import search path
-j <n> Number of commands to execute in parallel
-L <value> Add directory to library link search path
-l<value> Specifies a library which should be linked against
-migrate-keep-objc-visibility
When migrating, add '@objc' to declarations that would've been implicitly visible in Swift 3
-migrator-update-sdk Does nothing. Temporary compatibility flag for Xcode.
-migrator-update-swift Does nothing. Temporary compatibility flag for Xcode.
-module-cache-path <value>
Specifies the Clang module cache path
-module-link-name <value>
Library to link against when using this module
-module-name <value> Name of the module to build
-nostdimport Don't search the standard library import path for modules
-num-threads <n> Enable multi-threading and specify number of threads
-Onone Compile without any optimization
-Osize Compile with optimizations and target small code size
-Ounchecked Compile with optimizations and remove runtime safety checks
-output-file-map <path> A file which specifies the location of outputs
-O Compile with optimizations
-o <file> Write output to <file>
-parse-as-library Parse the input file(s) as libraries, not scripts
-parse-sil Parse the input file as SIL code, not Swift source
-parseable-output Emit textual output in a parseable format
-profile-coverage-mapping
Generate coverage data for use with profiled execution counts
-profile-generate Generate instrumented code to collect execution counts
-profile-use=<profdata> Supply a profdata file to enable profile-guided optimization
-remove-runtime-asserts Remove runtime safety checks.
-require-explicit-availability-target <target>
Suggest fix-its adding @available(<target>, *) to public declarations without availability
-require-explicit-availability
Require explicit availability on public declarations
-Rpass-missed=<value> Report missed transformations by optimization passes whose name matches the given POSIX regular expression
-Rpass=<value> Report performed transformations by optimization passes whose name matches the given POSIX regular expression
-runtime-compatibility-version <value>
Link compatibility library for Swift runtime version, or 'none'
-sanitize-coverage=<type>
Specify the type of coverage instrumentation for Sanitizers and additional options separated by commas
-sanitize=<check> Turn on runtime checks for erroneous behavior.
-save-optimization-record-path <value>
Specify the file name of any generated YAML optimization record
-save-optimization-record
Generate a YAML optimization record file
-save-temps Save intermediate compilation results
-sdk <sdk> Compile against <sdk>
-serialize-diagnostics Serialize diagnostics in a binary format
-static-executable Statically link the executable
-static-stdlib Statically link the Swift standard library
-static Make this module statically linkable and make the output of -emit-library a static library.
-suppress-warnings Suppress all warnings
-swift-version <vers> Interpret input according to a specific Swift language version number
-target-cpu <value> Generate code for a particular CPU variant
-target-variant <value> Generate code that may run on a particular variant of the deployment target
-target <value> Generate code for the given target
-tools-directory <directory>
Look for external executables (ld, clang, binutils) in <directory>
-track-system-dependencies
Track system dependencies while emitting Make-style dependencies
-use-ld=<value> Specifies the linker to be used
-verify-debug-info Verify the binary representation of debug output.
-version Print version information and exit
-vfsoverlay <value> Add directory to VFS overlay file
-v Show commands to run and use verbose output
-warn-implicit-overrides
Warn about implicit overrides of protocol members
-warn-swift3-objc-inference-complete
Warn about deprecated @objc inference in Swift 3 for every declaration that will no longer be inferred as @objc in Swift 4
-warn-swift3-objc-inference-minimal
Warn about deprecated @objc inference in Swift 3 based on direct uses of the Objective-C entrypoint
-warnings-as-errors Treat warnings as errors
-whole-module-optimization
Optimize input files together instead of individually
-working-directory <path>
Resolve file paths relative to the specified directory
-Xcc <arg> Pass <arg> to the C/C++/Objective-C compiler
-Xlinker <value> Specifies an option which should be passed to the linker
先写一段简单的 Swift 代码
import Foundation
print("hello, world")
然后用 -dump-ast
导出它的语法树,应该就能看到打印出来的抽象语法树。
swiftc -dump-ast main.swift
再来看看 Swift 中间代码长啥样,使用 -emit-sil
参数。
swiftc -emit-sil main.swift
至于其他命令,也是通过类似方式执行就能得到对应的结果。
基础语法
Constants 及 Variables
Swift 用 let
表示常量, var
表示变量,代码长下面这样。我看官方文档说 let
表示常量,我个人认为 let
表示的是一个变量不可变,当然还是以官方为准比较好。
下面第一行代码,如果 let a
但是并不指定类型也不赋值,就会报错
let a: Int
a = 11
let b = 12
var c = 11
c = 14
编译器会提示这样的错误
let a // Type annotation missing in pattern
标识符
标识符也就是 identifier
,譬如下面的 a
跟 test
就是标识符
let a = 10
func test() {}
Swift 语言的标识符不能使用数字、空白字符(譬如 tab、换行符)、箭头之类的特殊字符,不能用数字开头的这很好理解,因为编译器会很难区分当前读取的一连串字符到底是数字(譬如语言中的整型,浮点型)还是单纯的标识符。不过像下面这种代码是支持的
func 🐂🍺() -> Int {
return 666
}
print(🐂🍺())
值类型
Integers
Swift 的整型是 Int
表示,也有对应精度的 Int8
、Int16
、Int32
、Int64
、UInt8
、UInt16
、UInt32
、UInt64
,Int
对应 32 位平台上就会自动使用 Int32
,对应 64 位平台上就会使用 Int64
,观感上类似 objc 的 NSInteger
,如果要写一些早期 8bit 的游戏平台仿真器(譬如 NES)就会用到 UInt8
这个类型,通常在 iOS 开发中直接用 Int
就好。Swift
也可以使用类似下面这样的数值表示
let a = 16 // 十进制的 16
let b = 0b1_0000 // 二进制的 16
let c = 0o20 // 八进制的 16
let d = 0x10 // 十六进制的 16
Floating-Point Numbers
Swift 的浮点型也是可以套用其他语言的认知,分 Float
、Double
两种,另外浮点型支持科学计数法
let a = 12.0
let b = 1.2e1 // 科学计数法
a == b // true
let c = 0x06p1 // 6 * (2 ^ 1)
a == c // true
let d = 0x06p-1 // 6 * (2 ^ -1)
d == 3.0 // true
let e = 0x1.1p0 // (1 + 1.0 / 16.0) * (2 ^ 0)
Numeric Type Conversion
Swift 的数字(譬如 Int16
跟 Int8
,Int
跟 Double
)之间做运算,需要转成相同的类型。其实这些在其他语言看来是基本类型的东西(譬如 Int
)在 Swift 看来是结构体,它们的 +
号也是 Int
/Double
结构体上定义的静态方法,然后这个静态方法限定了左右两边相加的类型,所以如果要把一个整型跟一个浮点型相加,那就把其中一个数的值给另一个数相同类型的构造函数构造出实例,于是下面就变成了 Double(a) + b
let a = 3
let b = 0.14
// let result = a + b // Binary operator '+' cannot be applied to operands of type 'Int' and 'Double'
let result = Double(a) + b // 3.14
如果不指定类型,直接把两个 literal(字面量)相加,就不需要写构造
let result = 3 + 0.14 // 3.14
Tuples
苹果官方文档上有下面一段代码,因为我学过 Rust,一看这个东西,就感觉它跟 Rust 用法应该差不多。
let http404Error = (404, "Not Found")
应该是通过 http404Error.0
、http404Error.1
这种方式访问数据,然后之前用过一段时间 Swift,知道它也有模式匹配,所以肯定也能这样取数据
let (code, message) = http404Error
因为很多 OCaml 风格的语言(譬如 Rust, ReScript)都用 _
的符号表示不使用某个值,所以下面这段代码理所当然地从脑海中冒出来。
let (code, _) = http404Error
当然官方文档还给出了另一种构造跟访问元组的方式
let http200Status = (statusCode: 200, description: "OK")
print(http200Status.statusCode)
print(http200Status.1)
String
Swift 的字符串字面量直接用双引号包裹就行,这玩意还支持 +
之类的运算符,还有类似模板字符串的东西,多行字符串也是可以的。Swift 字符串操作太多了,后面再细看一下。
var str = "some str"
str += " end"
str = "\(str)." // "some str end."
str = """
line break
"""
Character
如果给一个字符串指定 Character
类型,就得到一个字符了,当然当前字符串超出一个字符,就会收到编译器错误
let char: Character = "🍺"
print(char) // 🍺
let c: Character = "123" // Cannot convert value of type 'String' to specified type 'Character'
未完待续