Swift 从入门到放弃-基础学习

1,896 阅读9分钟

介绍

最近打算重新学习一下 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-compile-flow.png

图中命令其实是独立的,这样画只是说明它经历过哪些环节,先是从 Swift 代码开始,编译器会对代码进行分词,解析,生成抽象语法树,这期间会进行语义分析,譬如类型检查之类的操作。 接着就是把生成的 ast 转成 sil,既然有 sil,那自然就是优化 sil,完了就生成 LLVMIR 码,然后通过 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

Swiftlet 表示常量, 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,譬如下面的 atest 就是标识符

let a = 10

func test() {}

Swift 语言的标识符不能使用数字、空白字符(譬如 tab、换行符)、箭头之类的特殊字符,不能用数字开头的这很好理解,因为编译器会很难区分当前读取的一连串字符到底是数字(譬如语言中的整型,浮点型)还是单纯的标识符。不过像下面这种代码是支持的

func 🐂🍺() -> Int {
    return 666
}
print(🐂🍺())

值类型

Integers

Swift 的整型是 Int 表示,也有对应精度的 Int8Int16Int32Int64UInt8UInt16UInt32UInt64Int 对应 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 的浮点型也是可以套用其他语言的认知,分 FloatDouble 两种,另外浮点型支持科学计数法

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 的数字(譬如 Int16Int8IntDouble)之间做运算,需要转成相同的类型。其实这些在其他语言看来是基本类型的东西(譬如 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.0http404Error.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'

未完待续