专注、坚持

《Advanced Swift》笔记

2021.08.03 by kingcos

介绍

  • ==结构相等(需要实现 Equatable 协议),===指针相等或者引用相等:
class Foo: Equatable {
    var bar: Int = 0
    
    static func == (lhs: Foo, rhs: Foo) -> Bool {
        lhs.bar == rhs.bar
    }
}

let f1 = Foo()
let f2 = Foo()

let falseValue = (f1 === f2) // false
let trueValue  = (f1 ==  f2) // true
  • Foundation 框架中的 Data 结构体是对引用类型 NSData 的封装,当 Data 将对其封装的 NSData 对象进行深拷贝
// Foundation

@frozen public struct Data {
    // ...
}

var d1 = "kingcos.me".data(using: .utf8)
var d2 = d1

d1?.removeAll()
print(d1!.count) // 0
print(d2!.count) // 10
  • 数组等集合类型只有当其中的元素满足值语义,其本身才具有值语义:
class Foo {
    var bar: Int = 0
}

let f = Foo()

let arr1 = [f]
let arr2 = arr1

arr2[0].bar = 100 // arr1[0].bar == 100

// ---

struct Baz {
    var bar: Int = 0
}

let b = Baz()

var arr3 = [b]    // ⚠️ 这里需要使用 var
let arr4 = arr3

arr3[0].bar = 100 // arr4[0].bar == 0
  • final 仅能够修饰类以及类中的方法、属性,用以确保其不被子类化
final class Foo { // 不得子类化
    final func finalFunc() {} // 子类不得重写
    final var finalVar = 0    // 子类不得重写
}
  • 高阶函数(High-order function):接受别的函数作为参数的函数。
  • 方法(Method):定义在类或者协议中的函数,具有一个隐式参数 self;不是方法的函数有时称作自由函数(Free function)。
  • 对于静态派发(Statically dispatched)的调用,编译器可能会内联优化。
  • 类和协议上的方法可能是动态派发(Dynamically dispatched),编译器在编译时不需要知道哪个方法将被调用。在 Swift 中,动态特性由 vtable(虚表)或 selectorobjc_msgSend 来完成,前者的处理方式类似 Java 或 C++,后者则只针对 @objc 修饰的类和协议上的方法。
  • 函数重载和泛型中的方法在编译时确定,方法重写在运行时确定。

内建集合类型

  • 可以使用 reduce(_:) 来实现 mapfilter,但需要注意这里的性能问题:在闭包中每次为数组追加元素时,都需要重新创建数组并复制元素,再叠加 reduce(_:) 本身 O(n),最终时间复杂度将为 O(n^2);这里也可使用 reduce(inout:) 避免数组重复创建和复制带来的损耗,重新将时间复杂度降回 O(n);另可参考《Arrays, Linked Lists and Performance》。
extension Array {
    // O(n^2)
    func rdc_map<T>(_ transform: (Element) -> T) -> [T] {
        reduce([]) { $0 + [transform($1)] }
    }
    
    // O(n^2)
    func rdc_filter(_ isIncluded: (Element) -> Bool) -> [Element] {
        reduce([]) { isIncluded($1) ? $0 + [$1] : $0 }
    }

    // O(n)
    func opt_rdc_filter(_ isIncluded: (Element) -> Bool) -> [Element] {
        reduce(into: []) {
            if isIncluded($1) { $0.append($1) }
        }
    }
}
  • flatMap 通过使用 append(contentsOf:)(参数为 Sequence 代表序列)代替了 mapappend(_:)(参数为 Any 代表元素)实现了降维。
  • 切片(Slice)类型是数组的一种表示方式,其背后仍然为数组,因此也不会引起元素复制;操作切片时建议使用 startIndexendIndex
let arr = [1, 2, 3]
let slice = arr[2...] // [3]

// slice[0] // Fatal error: Index out of bounds
slice[slice.startIndex] // 3
  • 字典上的一些「小众」方法:
var dict1 = ["web": "github.com/kingcos", "wxpub": "萌面大道"]
var dict2 = ["web": "kingcos.me"]

// ["web": "kingcos.me", "wxpub": "萌面大道"]
dict1.merge(dict2) { $1 }

// [2: "b", 3: "c", 1: "a"]
let dict3 = Dictionary(uniqueKeysWithValues: zip([1, 2, 3], ["a", "b", "c"]))

// ["web": "v: github.com/kingcos", "wxpub": "v: 萌面大道"]
let dict4 = dict1.mapValues { return "v: \($0)" }
  • Swift 标准库中的通用哈希函数使用随机种子(Randomly seeding)作为输入之一,用来防止哈希洪泛式拒绝服务攻击(Hash-flooding attack),即无法保证在程序的不同执行过程中哈希值是相等的,可以使用 SWIFT_DETERMINISTIC_HASHING=1 环境变量禁用随机种子:
➜  ~ swift
Welcome to Apple Swift version 5.4.2 (swiftlang-1205.0.28.2 clang-1205.0.19.57).
Type :help for assistance.
  1> 10.hashValue
$R0: Int = 8376577764884252349
[1]  + 75023 suspended  swift

➜  ~ swift
Welcome to Apple Swift version 5.4.2 (swiftlang-1205.0.28.2 clang-1205.0.19.57).
Type :help for assistance.
  1> 10.hashValue
$R0: Int = -5587253586683290564
  • 不建议使用引用类型变量作为字典的键,因为当键发生改变时,其哈希值和(或)相等性也会被改变,将无法再在字典中找到:
class Foo: Hashable { 
    var f: Int = 0
    
    static func == (lhs: Foo, rhs: Foo) -> Bool {
        lhs.f == rhs.f
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(f)
    }
}

let foo = Foo()
foo.f = 1
let dict = [foo: "foo"] // dict[foo] == Optional("foo")
foo.f = 100             // dict[foo] == nil