Preface
《Swift 拾遗》是一个关于 Swift 的新文章专辑,这个系列的文章将不会涉及基本语法的层面,而是尝试从底层「拾」起之前所忽视的内容。那么作为起始篇,随着整个系列的进行,其中「遗」漏的基本使用内容将在本文中得到补充。
Contents
Void
按照 Swift 标准库的定义,Void
即空元组 ()
:
public typealias Void = ()
函数重载(Overload)
Swift 中的函数重载有一些「坑」,因此尽量不要产生命名歧义性(比如结合默认参数值等用法)。
- 另可参考《Obj-C 中的重载与重写》
@discardableResult
Swift 是一门要求很严格的语言,当函数的返回值未被使用到时,编译器就会提示相关的警告。我们可以使用 @discardableResult
将函数声明为可丢弃结果,即可告知编译器不产生警告:
import Foundation
func foo() -> String {
return "kingcos.me"
}
@discardableResult
func bar() -> String {
return "kingcos.me"
}
foo() // WARNING: Result of call to 'foo()' is unused
// 当然也可以赋值到占位符 _ 以避免警告
_ = foo()
bar()
swiftc
swiftc
是 Swift 编译器(前端),位于 Xcode 中 Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/
目录,其本质是同目录下 swift
的符号链接:
-rwxr-xr-x 1 root wheel 95341344 Jul 2 14:37 swift
lrwxr-xr-x 1 root wheel 5 Jul 18 00:20 swiftc -> swift
- 生成语法树:
swiftc -dump-ast demo.swift
- 生成最简洁的 SIL 代码:
swiftc -emit-sil demo.swift
- 生成 LLVM IR 代码:
swiftc -emit-ir demo.swift -o demo.ll
- 生成汇编代码:
swiftc -emit-assembly demo.swift -o demo.s
- 另可参考《[译] Swift 编译器》
隐式原始值
当 Swift 中的枚举原始值(Raw Value)类型为 Int
或 String
时,其将获得默认值,即隐式原始值(Implicitly Assigned Raw Values):
enum IntFoo: Int {
case first
case second = 200
case third
case fourth = 400
case fifth
}
print(IntFoo.first.rawValue) // 0
print(IntFoo.second.rawValue) // 200
print(IntFoo.third.rawValue) // 201
print(IntFoo.fourth.rawValue) // 400
print(IntFoo.fifth.rawValue) // 401
enum StrFoo: String {
case first
case second = "2"
case third
}
print(StrFoo.first.rawValue) // first
print(StrFoo.second.rawValue) // 2
print(StrFoo.third.rawValue) // third
递归枚举
递归枚举(Recursive Enumeration)指的是枚举的关联值(Associated Value)类型使用到了该枚举类型本身,此时需要 indirect
修饰:
indirect enum IndirectBar {
case first(Int)
case second(IndirectBar)
}
enum Bar {
case first(Int)
indirect case second(Bar)
}
let bar1 = IndirectBar.second(.second(.second(.first(0))))
let bar2 = Bar.second(.second(.second(.first(0))))
print(bar1) // second(demo.IndirectBar.second(demo.IndirectBar.second(demo.IndirectBar.first(0))))
print(bar2) // second(demo.Bar.second(demo.Bar.second(demo.Bar.first(0))))
MemoryLayout
不同于 Obj-C/C++,Swift 变得更加注重安全且无法直接使用 C/C++ 中的 API,这使得我们剖析 Swift 底层时有些「不踏实」。而 MemoryLayout
正是来帮助我们描述类型的内存布局,提供诸如大小、步进、以及对齐的信息:
// 支持泛型
// size:实际占用的空间大小
print(MemoryLayout<Int>.size) // 8
// stride:最终分配的空间大小
print(MemoryLayout<Int>.stride) // 8
// alignment:内存对齐参数(最终分配的空间大小即其倍数)
print(MemoryLayout<Int>.alignment) // 8
var intFoo = 100 // 16 * 6 + 4 => 0x0000000000000064
var intBar = 18 // 16 * 1 + 2 => 0x0000000000000012
print(MemoryLayout.size(ofValue: intFoo)) // 8
print(MemoryLayout.stride(ofValue: intFoo)) // 8
print(MemoryLayout.alignment(ofValue: intFoo)) // 8
// 打印值类型变量的内存地址
withUnsafePointer(to: &intFoo) { print("\($0)") } // 0x0000000100003088
struct StructFoo {
var a = 1 // 0 + 1 => 0x0000000000000001
var b = 1 // 0 + 1 => 0x0000000000000001
}
var foo = StructFoo()
// 打印结构体类型变量的内存地址
withUnsafeMutablePointer(to: &foo) { print("\($0)") } // 0x0000000100003098
// x86_64 / arm64 小端:高位字节存放在高位地址
// eg. 0x0000000000000064,64 为低位,00 为高位,内存地址从左至右依次升高,64 低位字节存放在低位地址即开头
// (lldb) memory read 0x0000000100003088
// 0x100003088: 64 00 00 00 00 00 00 00 12 00 00 00 00 00 00 00 d...............
// 0x100003098: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
// (lldb) memory read 0x0000000100003098
// 0x100003098: 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
// 0x1000030a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
main
Swift 项目中没有显式的 main
函数作为整个程序的入口,但当我们断点在 main.swift
中时,Debug Navigator 中仍将展示 0 main
,此时打开汇编也会在第一行展示demo`main:
。需要注意的是,声明在 main.swift
的变量将类似全局变量,存储在内存的全局区(数据段),可以直接在其它文件中使用:
// main.swift
var gloA = 100
withUnsafePointer(to: &gloA) { print("\($0)") } // 0x00000001000031c0
下标(Subscript)
下标是指可以在类、结构体、或枚举中定义 subscript
方法,即可使相应实例获得通过下标访问的能力:
class Foo {
var a = 10
var b = 20
subscript(index: Int) -> Int { // 省略 `func` 关键字
get { // 通过 subscript 获取,类似计算属性
switch index {
case 0:
return a
case 1:
return b
default:
fatalError("Index out of range.")
}
}
set { // 通过 subscript 设置,类似计算属性
switch index {
case 0:
a = newValue
case 1:
b = newValue
default:
fatalError("Index out of range.")
}
}
}
}
var foo = Foo()
foo[0] = 100
foo[1] = 200
print(foo[0]) // 100
print(foo[1]) // 200
当不实现 set
时,将变成只读下标,即只可以通过下标访问而不能更改。下标支持设置显式的参数标签,以及支持同时多个下标索引;也支持针对于类型的下标,只需要在声明前添加 static
或 class
关键字修饰即可(但需要注意由于 static
修饰的方法将不能被子类重写,下标也同理):
class Bar {
var colmunA = [1, 2]
var columnB = [10, 20]
static var c = 30
static var d = 40
subscript(i index: Int, row: Int) -> Int { // 只读下标 & 多下标索引
get {
switch index {
case 0:
return colmunA[row]
case 1:
return columnB[row]
default:
fatalError("Index out of range.")
}
}
}
static subscript(index: Int) -> Int {
switch index {
case 0:
return c
case 1:
return d
default:
fatalError("Index out of range.")
}
}
}
print(Bar()[i: 0, 1]) // 10
print(Bar[0]) // 30
当为只读下标时,通过下标获取的类型根据是结构体还是类的情况有些许不同:
class ClassFoo {
var a = 10
}
struct StructBar {
var b = 20
}
class BazByClass {
var foo = ClassFoo()
subscript(index: Int) -> ClassFoo {
get { foo }
}
}
class BazByStruct {
var bar = StructBar()
subscript(index: Int) -> StructBar {
get { bar }
}
}
var baz1 = BazByClass()
baz1[0].a = 100
// baz1[0] = ClassFoo() // ERROR: Cannot assign through subscript: subscript is get-only
print(baz1[0].a)
var baz2 = BazByStruct()
// baz2[0].b = 200 // ERROR: Cannot assign to property: subscript is get-only
print(baz2[0].b)
当只读下标获取的类型为类时(引用类型),只读只针对于指向堆空间实例的地址,即无法设置指向为新的实例,但仍可以读写堆的内存空间;而当只读下标获取的类型为结构体时(值类型),将无法通过下标设置结构体变量的值,那么想要修改就要通过 set
实现:
class BazByStruct2 {
var bar = StructBar()
subscript(index: Int) -> StructBar {
get { bar }
set {
bar = newValue
}
}
}
var baz3 = BazByStruct2()
withUnsafeMutablePointer(to: &baz3.bar) { print("\($0)") } // 0x0000000100523ac0
baz3[0].b = 200
withUnsafeMutablePointer(to: &baz3.bar) { print("\($0)") } // 0x0000000100523ac0
print(baz3[0].b) // 200
static 与 class
static
和 class
均可以在 class
类型中修饰方法或属性,即类型方法或类型属性。但两者的不同是,static
修饰的方法或属性子类将不能重写:
class Foo {
static var staticProp = 1
static func staticFunc() { }
class var classProp: Int { 1 }
class func classFunc() { }
}
class SubFoo: Foo {
// static 修饰的属性 / 方法不可被重写
// ERROR: Cannot override static property
// static var staticProp: Int {
// get { super.staticProp }
// set { super.staticProp = newValue }
// }
// ERROR: Cannot override static method
// static func staticFunc() { }
// class 修饰的属性 / 方法可被重写
override class var classProp: Int {
get { super.staticProp }
set { super.staticProp = newValue + 1 }
}
override static func classFunc() {}
}
class SubSubFoo: SubFoo {
// 二次重写时使用 class 修饰,子类仍可重写
override class var classProp: Int {
get { super.staticProp }
set { super.staticProp = newValue + 2 }
}
// 二次重写时使用 static 修饰,子类将不可重写
// ERROR: Cannot override static method
// override class func classFunc() {}
}
Any 与 AnyObject
Swift 中的 Any
类型可以代表包括结构体、枚举、类、函数等任意类型;AnyObject
类型仅可以代表任意 class
类型。
struct Foo {
var foo = 1
}
class Bar {
var bar = 2
}
class AnotherBar {
var bar = 3
}
func baz() {}
var any: Any = 10
any = 3.14
any = Foo()
any = Bar()
any = baz
var anyObject: AnyObject = Bar()
anyObject = AnotherBar()
// ERROR: Value of type 'Foo' expected to be an instance of a class or class-constrained type in assignment
// anyObject = Foo()
当 Swift 的协议遵守 AnyObject
时,该协议将只能被类遵守:
protocol ProtocolA {
func a()
}
protocol ProtocolB: AnyObject {
func b()
}
// 协议遵守 class 时也将只能被类遵守
protocol ProtocolC: class {
func c()
}
struct Foo: ProtocolA {
func a() {}
}
// class Bar: ProtocolA, ProtocolB, ProtocolC {
class Bar: ProtocolA & ProtocolB & ProtocolC {
func a() {}
func b() {}
func c() {}
}
- 另可参考《Obj-C & Swift 的类型内省与反射》
T.self、T.Type、Self 与 type(of:)
若 T
为某一类型,T.self
是元类型(Metatype)的指针,类型为 T.Type
,元类型中存储了类型本身的信息:
class Foo { }
struct Bar { }
var selfInt = Int.self // Int.Type
var selfFoo: Foo.Type = Foo.self // Foo.Type
var selfBar: Bar.Type = Bar.self // Bar.Type
var intA = intSelf.init(5) // 5: Int
AnyClass
是 AnyObject.Type
的类型别名,即任意 class
类型的元类型指针:
// Swift / Misc
public typealias AnyClass = AnyObject.Type
我们可以尝试将其转换为不同的元类型指针,用以构造:
class Foo {
required init(_ str: String) { }
}
func initAnyClass(_ ac: AnyClass) -> AnyObject {
if let ac = ac as? Foo.Type {
return ac.init("ac") // 编译器已经替我们尝试约束 Foo 所必须具备(required)的 init 方法
}
fatalError()
}
print(initAnyClass(Foo.self)) // demo.Foo
Self
代表当前所在的类型本身(类似于 Obj-C 中的 instancetype
),可定义为函数的返回值:
protocol Protocol {
// 在协议中,Self 可作为函数参数使用
func function(_ param: Self)
}
class Foo: Protocol {
func foo() {}
// 必须声明为 required,防止子类声明其它构造方法导致 init() 丢失后无法被调用
required init() { }
func foo() -> Self {
// type(of value: T) -> Metatype :返回参数的类型
// 即使子类也可获取类型,并初始化
return type(of: self).init()
}
func function(_ param: Foo) { }
}
class SubFoo: Foo { }
struct Bar {
func bar() -> Self {
return type(of: self).init()
}
}
type(of:)
在 Swift 中看似被认为是函数,带有参数与返回值,但从现有汇编中可以得出其本质上并非函数调用:
class Foo {
func foo() {}
}
var f = Foo()
var fType = type(of: f) // Foo.Type;BREAKPOINT 🔴
// Foo.self 为指向元类型的指针,即类型信息的地址,因此与 type(of:) 返回值相同
print(Foo.self == fType) // true
运行至断点处并查看汇编:
demo`main:
0x100002ad0 <+0>: pushq %rbp
0x100002ad1 <+1>: movq %rsp, %rbp
0x100002ad4 <+4>: pushq %r13
0x100002ad6 <+6>: subq $0x58, %rsp
0x100002ada <+10>: xorl %eax, %eax
0x100002adc <+12>: movl %eax, %ecx
0x100002ade <+14>: movl %edi, -0x24(%rbp)
0x100002ae1 <+17>: movq %rcx, %rdi
0x100002ae4 <+20>: movq %rsi, -0x30(%rbp)
0x100002ae8 <+24>: callq 0x100002b80 ; type metadata accessor for demo.Foo at <compiler-generated>
0x100002aed <+29>: movq %rax, %r13
0x100002af0 <+32>: movq %rdx, -0x38(%rbp)
0x100002af4 <+36>: callq 0x100002c20 ; demo.Foo.__allocating_init() -> demo.Foo at main.swift:3
; 将 rip(0x100002b00)+ 0x57b8(0x1000082B8)存储在 rcx 寄存器中(全局变量 f)
0x100002af9 <+41>: leaq 0x57b8(%rip), %rcx ; demo.f : demo.Foo
0x100002b00 <+48>: xorl %r8d, %r8d
0x100002b03 <+51>: movl %r8d, %edx
0x100002b06 <+54>: movq %rax, 0x57ab(%rip) ; demo.f : demo.Foo
-> 0x100002b0d <+61>: movq %rcx, %rdi
0x100002b10 <+64>: leaq -0x20(%rbp), %rsi
0x100002b14 <+68>: movl $0x20, %eax
0x100002b19 <+73>: movq %rdx, -0x40(%rbp)
0x100002b1d <+77>: movq %rax, %rdx
0x100002b20 <+80>: movq -0x40(%rbp), %rcx
0x100002b24 <+84>: callq 0x100003d12 ; symbol stub for: swift_beginAccess
; 移动 rip(0x100002b30)+ 0x5788(0x1000082B8,全局变量 f,即 Foo 类型对象的指针)至 rax 寄存器
0x100002b29 <+89>: movq 0x5788(%rip), %rax ; demo.f : demo.Foo
0x100002b30 <+96>: movq %rax, %rcx
0x100002b33 <+99>: movq %rcx, %rdi
; 移动 rax 寄存器内容至 rbp - 0x48
0x100002b36 <+102>: movq %rax, -0x48(%rbp)
0x100002b3a <+106>: callq 0x100003d2a ; symbol stub for: swift_retain
0x100002b3f <+111>: leaq -0x20(%rbp), %rdi
0x100002b43 <+115>: movq %rax, -0x50(%rbp)
0x100002b47 <+119>: callq 0x100003d1e ; symbol stub for: swift_endAccess
; 移动 rbp - 0x48 内容至 rax 寄存器
0x100002b4c <+124>: movq -0x48(%rbp), %rax
; 取 rax 寄存器内容所指向的内存空间的内容(前八个字节,即类型信息)至 rcx 寄存器
0x100002b50 <+128>: movq (%rax), %rcx
0x100002b53 <+131>: movq %rax, %rdi
; 移动 rcx 寄存器内容至 rbp - 0x58
0x100002b56 <+134>: movq %rcx, -0x58(%rbp)
0x100002b5a <+138>: callq 0x100003d24 ; symbol stub for: swift_release
0x100002b5f <+143>: xorl %eax, %eax
; 移动 rbp - 0x58 内容至 rcx 寄存器
0x100002b61 <+145>: movq -0x58(%rbp), %rcx
; 移动 rcx 寄存器内容(前八个字节)至 rip + 0x5754(全局变量 fType)
0x100002b65 <+149>: movq %rcx, 0x5754(%rip) ; demo.fType : demo.Foo.Type
0x100002b6c <+156>: addq $0x58, %rsp
0x100002b70 <+160>: popq %r13
0x100002b72 <+162>: popq %rbp
0x100002b73 <+163>: retq
CustomStringConvertible
public protocol CustomStringConvertible {
var description: String { get }
}
public protocol LosslessStringConvertible : CustomStringConvertible {
init?(_ description: String)
}
public protocol CustomDebugStringConvertible {
var debugDescription: String { get }
}
CustomStringConvertible
协议中只有一个 description
属性,类似 Obj-C 中 NSObject
的 description
方法,Swift 中的类型可以通过实现该协议在需要打印对象的时候进行自定义表示。LosslessStringConvertible
协议继承自 CustomStringConvertible
,提供了从表示到对象的可失败构造方法。CustomDebugStringConvertible
则提供了更多 Debug 下常用的对象打印,如 debugPrint
与 LLDB 的 po
命令:
// Swift / Misc
struct Foo: CustomStringConvertible,
LosslessStringConvertible,
CustomDebugStringConvertible {
var foo: String
init?(_ description: String) {
foo = description
}
var description: String {
"Foo - \(foo)"
}
var debugDescription: String {
"Debug - Foo - \(foo)"
}
}
let f = Foo("foo")!
print(f) // Foo - foo,BREAKPOINT 🔴
debugPrint(f) // Debug - Foo - foo
// LLDB:
// (lldb) po f
// ▿ Debug - Foo - foo
// - foo : "foo"
// (lldb) p f
// (demo.Foo) $R4 = (foo = "foo")
默认情况
当未实现
CustomStringConvertible
协议时,debugPrint
、po
均可正常使用,表现如下;当仅实现CustomStringConvertible
协议时,debugPrint
与po
将默认以实现的description
为准,表现如下:struct Bar { var bar: String } let bar = Bar(bar: "bar") print(bar) // Bar(bar: "bar") debugPrint(bar) // demo.Bar(bar: "bar") struct Baz: CustomStringConvertible { var baz: String var description: String { "Baz - \(baz)" } } let baz = Baz(baz: "baz") print(baz) // Baz - baz,BREAKPOINT 🔴 debugPrint(baz) // Baz - baz // LLDB: // (lldb) po bar // ▿ Bar // - bar : "bar" // (lldb) po baz // ▿ Baz - baz // - baz : "baz"
断言(assert)
我们常在 Debug 模式下使用断言来保证代码的健壮性。默认情况下,Swift 中的断言仅在 Debug 模式下生效,但我们也可以通过编译选项配置:
Other Swift Flags | Description |
---|---|
-assert-config Release | 断言失效 |
-assert-config Debug | 断言生效 |