Skip to main content

Swift 踩坑记

·199 words·1 min· 📖

开发过程中遇到的一些坑,记录一下。

Optional #

可选类型作为 Swift 的一个标志性语法,有一些反直觉的坑。

nil 判断 #

一个数组,比如这样的类型 [Int?],想移除它最后的空值,直觉的写法是:

var ans: [Int?] = [1,2,nil,nil]
while ans.last == nil {
    ans.removeLast()
}

然而!结果并不是想的那样,nil 并没有被移除。为什么?

我们去看 last 方法的定义就会发现:

@inlinable public var last: Element? { get }

返回值是 Element? 类型,在这里就是 Int?? 或者 Optional<Optional<Int>> 类型

嵌套的 Optional 总是非空的

所以上面的写法要改成:

while let last = ans.last, last == nil {
    ans.removeLast()
}

compactMap #

我们知道 compactMap 非常有用,它的原型是:

/*
@return
  An array of the non-nil results of calling transform 
  with each element of the sequence.

@param transform
  A closure that accepts an element of this sequence 
  as its argument and returns an optional value.
*/
func compactMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

比如,一个数组 [3,9,20,nil,nil,15,7],使用 compactMap 后:

let nums = [3,9,20,nil,nil,15,7]
let ans = nums.compactMap { $0 }
// [3,9,20,15,7]

Good !

但是!如果接收 compactMap 结果的类型是 [Int?] 类型的呢?

let ans: [Int?] = nums.compactMap { $0 }
// [3,9,20,nil,nil,15,7]

这个时候再加一层 map 才会得到预期的结果

let ans: [Int?] = nums.compactMap { $0 }.map { $0 }
// [3,9,20,15,7]

Why ?

Swift 源码 中发现:

@inlinable // lazy-performance
  public func compactMap<ElementOfResult>(
    _ transform: @escaping (Elements.Element) -> ElementOfResult?
  ) -> LazyMapSequence<
    LazyFilterSequence<
      LazyMapSequence<Elements, ElementOfResult?>>,
    ElementOfResult
  > {
    return self.map(transform).filter { $0 != nil }.map { $0! }
  }

看起来还挺正常?

我怀疑编译器根据返回类型,直接赋值而没有执行 compactMap 方法本身。