Ninjiacoder Swift Lover, Fullstack Developer

SwiftUI 的 DSL 语法分析(一)some View

范型 Generics

范型的意义在于可复用函数,比如要求写一个交换两个整数的方法,很简单:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

那我再要求你写一个交换两个字符串,交换两个浮点数的方法呢?再重新写一遍?很明显是不可取的,所以编程语言都会有一种解决方案叫做范型,比如这里就可以用范型来解决这个问题。

func swapTwo<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

swift 标准库中就有 swap 方法,就是用范型写的。

@inlinable public func swap<T>(_ a: inout T, _ b: inout T)

关于更多范型的介绍可以查看参考文章 4 中的官方文档。

Opaque Result Types

前言

可以看到,SwiftUI 的模板代码中

struct ContentView : View {
    var body: some View {
        Text("Hello World")
    }
}

这里的 some View 就是这一小节的主题:Opaque Result Types,可以看到 SwiftUI 中的 View 作为一个协议出现

public protocol View : _View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required `body` property.
    associatedtype Body : View

    /// Declares the content and behavior of this view.
    var body: Self.Body { get }
}

在 Swift 范型中,associatedtype 是关联类型的关键字,不能直接返回。所以在 Swift5.1 之前,若要绕开 associatedtype 的约束,需要写明真实类型,应该这么写

struct ContentView : View {
    var body: Text {
        Text("Hello World")
    }
}

为了绕开这个限制,Swift 引入了 Opaque Result Types ,即这里看到 some View

Opaque Result Types 提出的原因

Type-level abstraction is missing for function returns 比如这样一段代码

func zim<T: P>() -> T { ... }

这意味着 zim 的返回值由调用者决定

let x: Int = zim() // T == Int chosen by caller

let y: String = zim() // T == String chosen by caller

所以当我们需要一个特定的返回值时,要么就写死这个类型,否则如果使用范型则它的类型将由调用者来决定。

提出的解决方案

于是,Swift 引入了这样的语法:some Protocol,举个例子

protocol Shape {
  func draw(to: Surface)

  func collides<Other: Shape>(with: Other) -> Bool
}

struct Rectangle: Shape { /* ... */ }
struct Circle: Shape { /* ... */ }

struct Union<A: Shape, B: Shape>: Shape {
  var a: A, b: B
  // ...
}

struct Intersect<A: Shape, B: Shape>: Shape {
  var a: A, b: B
  // ...
}

struct Transformed<S: Shape>: Shape {
  var shape: S
  var transform: Matrix3x3
  // ...
}

protocol GameObject {
  // The shape of the object
  associatedtype Shape: Shapes.Shape

  var shape: Shape { get }
}

这时我们想写一个八角星

struct EightPointedStar: GameObject {
  var shape: Union<Rectangle, Transformed<Rectangle>> {
    return Union(Rectangle(), Transformed(Rectangle(), by: .fortyFiveDegrees)
  }
}

原来我们得指明这个返回的类型 Union<Rectangle, Transformed<Rectangle>>  很长而且不便于阅读。 引入 some Protocol 的句法后就变得简单多了。

struct EightPointedStar: GameObject {
  var shape: some Shape {
    return Union(Rectangle(), Transformed(Rectangle(), by: .fortyFiveDegrees)
  }
}

在行为上,opaque type 很像是逆范型(reverse generic)

func generic<T: Shape>() -> T { ... }

let x: Rectangle = generic() // T == Rectangle, chosen by caller
let x: Circle = generic() // T == Circle, chosen by caller

// Strawman syntax
func reverseGeneric() -> <T: Shape> T { return Rectangle(...) }

let x = reverseGeneric() // abstracted type chosen by reverseGeneric's implementation

// Proposed syntax
func reverseGeneric() -> some Shape { return Rectangle(...) }

let x = reverseGeneric() // abstracted type chosen by reverseGeneric's implementation

参考文章

  1.  SwiftUI 的 DSL 语法分析 
  2.  What’s new in Swift 5.1 
  3.  0244-opaque-result-types 
  4. Swift 泛型