Swift中的param pack

230 阅读4分钟

前言介绍

Swift中已经有someany两个关键字用于配合协议使用,来约束使用者的类型必须符合协议

some

some关键字是在Swift5.1中引入,用于配合协议使用,创建不透明类型。在编译器看来,传入的参数依然是具体的类型,编译器使用了一个唯一的隐式类型接收这个类型,只不过编译器保证这个类型是遵循了这个协议的。它和使用泛型约束语法没任何区别

所以以下三种代码是等价的

// 使用简短泛型语法,约束T遵守协议
func wash<T: Vehicle>(_ vehicle: T) {}

// 使用完整泛型语法,约束T遵守协议
func wash<T>(_ vehicle: T) where T: Vehicle {}

// 使用some关键字,约束 匿名的不透明类型 遵守协议
// 因为这个类型在编译器看来和普通泛型类型的实参没任何区别。所以叫不透明类型
func wash(_ vehicle: some Vehicle)  {}

some关键字可以用于函数参数、变量类型、类型修饰、函数返回值等

// 函数参数使用some
func funcUseSome(_ v: some Vehicle) {}
// 变量使用some
let varUseSome: some Vehicle = Car()
// 函数返回值类型使用some
func createSomeVehicle() -> some Vehicle {
    return Car()
}
// 数组使用some
let arrUseSome: [some Vehicle] = []

不透明类型表示的隐式类型每次都是唯一的。下面的代码,一旦car变量就是一个隐式的不透明类型。

即使第二行代码又给car赋值了Car实例,这个Car类型也和上面生成的不透明类型不一样,会报错

var car: some Vehicle = Car()

// Cannot assign value of type 'Car' to type 'some Vehicle'
car = Car()

any

any是Swift5.6引入的,也是配合协议使用。它和some不同的是,它会将类型包装成existantial container。并且针对值类型和引用类型分别包装成不同容器类型

image.png

let myCar: any Vehicle = Car()
func wash(_ vehicle: any Vehicle)  {}

any不会和some一样生成不透明的唯一类型,它最终就是Container类型,可以重复的赋值,只要符合协议就行

 var myCar: any Vehicle = Car()
 // 这里还可以继续赋值
 myCar = Bus()
 myCar = Car()
 
// 函数的if分支里,只要是符合协议的类型,都可以返回
func createAnyVehicle(isPublicTransport: Bool) -> any Vehicle {
    if isPublicTransport {
        return Bus()
    } else {
        return Car()
    }
}

param pack

some 可以在保证在编译期就知道具体的类型,效率高。但它无法在集合中存放不同的符合协议的类型

any 把所有类型包装成existential container,极大的提高了灵活性,但同时也降低了效率

param pack 在Swift5.9引入,可以看作是对some的增强。这样我们在写接收不确定数量的泛型的函数时,就不用写多个重载函数了,用一个就可以搞定

没有param pack之前, 我们如果要支持元组的比较,要写N多个重载函数

func == (lhs: (), rhs: ()) -> Bool

func == <A, B>(lhs: (A, B), rhs: (A, B)) -> Bool where A: Equatable, B: Equatable

func == <A, B, C>(lhs: (A, B, C), rhs: (A, B, C)) -> Bool where A: Equatable, B: Equatable, C: Equatable

// and so on, up to 6-element tuples

有了param pack之后,我们只需要写下面的一个函数就可以了。并且在Swift6.0 param pack支持了for in 让代码更加简洁

// 使用 each Element引入不同的泛型形参
func == <each Element: Equatable>(lhs: (repeat each Element), 
                                  rhs: (repeat each Element)) -> Bool {
  // 使用repeat each Element重复不同数量的泛型形参
  // 编译器会保证lhs和rhs中泛型形参的数量是相等的
  for (left, right) in repeat (each lhs, each rhs) {
    guard left == right else { return false }
  }
  return true
}

一个应用实例

我们有一个协议,可以产生任意类型的值

protocol ValueProducer {
  associatedtype Value: Codable
  func evaluate() -> Value
}

我们有任意个可能产生值或产生错误的生产者,我们想要获取所有产生的值,将错误过滤

func evaluateAll<each V: ValueProducer, each E: Error>(result: repeat Result<each V, each E>) -> [any Codable] {
  var evaluated: [any Codable] = []
  for case .success(let valueProducer) in repeat each result {
    evaluated.append(valueProducer.evaluate())
  }

  return evaluated
}

使用的时候可以这样

// 产生整数类型的值
struct IntProducer: ValueProducer {
  let contained: Int
  init(_ contained: Int) {
    self.contained = contained
  }

  func evaluate() -> Int {
    return self.contained
  }
}
// 产生bool类型的值
struct BoolProducer: ValueProducer {
  let contained: Bool
  init(_ contained: Bool) {
    self.contained = contained
  }

  func evaluate() -> Bool {
    return self.contained
  }
}

struct SomeError: Error {}

print(evaluateAll(result:
Result<SomeValueProducer,SomeError>.success(SomeValueProducer(5)),
Result<SomeValueProducer, SomeError>.failure(SomeError()),
Result<BoolProducer, SomeError>.success(BoolProducer(true))))

// [5, true]

参考资料

  1. 参数包param pack
  2. 关于some和any
  3. Swift中 Parameter Pack的妙用