Swift

  1. Struct or Class

结构体和类

在Swfit中,什么时候用结构体,什么时候用类?

到底是用类的做法优于用结构体,还是用结构体的做法优于类。函数式编程倾向于值类型,面向对象编程更喜欢类。
在Swift 中,类和结构体有许多不同的特性。下面是两者不同的总结:

类支持继承,结构体不支持。
类是引用类型,结构体是值类型
并没有通用的规则决定结构体和类哪一个更好用。一般的建议是使用最小的工具来完成你的目标,但是有一个好的经验是多使用结构体,除非你用了继承和引用语义。

注意:在运行时,结构体的在性能方面更优于类,原因是结构体的方法调用是静态绑定,而类的方法调用是动态实现的。这就是尽可能得使用结构体代替类的又一个好的原因。

class 与 struct 的主要区别:类是引用类型,而结构体是值类型。

  1. underscore in function parameters 在Swift中,下划线(_)通常用于函数参数列表中作为参数标签的占位符。具体来说,使用下划线作为参数标签可以将函数参数列表中的参数标签省略,从而使函数在调用时更加简洁。

Some and Any

在 Swift 5.7 中,any 和 some 关键词具有不同的用途。any 关键词用于创建存在类型(existential type),它表示一个遵循特定协议的任意类型。在 Swift 5.7 中,创建存在类型时,必须使用 any 关键词,否则会出现编译错误。

与之相反,some 关键词用于创建不透明类型(opaque type),它表示一个遵循特定协议的具体类型,但该类型在编译时是未知的。some 关键词在 Swift 5.1 中引入,对于 SwiftUI 的 View 协议至关重要,因为 View 协议定义了关联类型,不能直接用作类型。简而言之,any 关键词表示一个遵循特定协议的任意类型,而 some 关键词表示一个遵循特定协议的具体但未知类型。这两个关键词都应用于协议,但它们在泛型和协议处理方面有着不同的作用。

some

“some” keyword: The some keyword was introduced in Swift 5.1 and is an improvement in generics world. The keyword is used together with a protocol to create something that is called an “opaque type”.

An opaque type is a way to return a type without needing to provide details on the concrete type itself(The opaque type represents something that is conformed to a specific protocol). It limits what callers need to know about the returned type, only exposing information about its protocol compliance.

When we use an opaque type, its exact type is going to be calculated by the compiler based on the actual returned value (the same as using the angle brackets or a trailing where clause at the function signature. for more search about generics).

So we use the somekeyword on a variable, we are telling the compiler that we are working on a specific concrete type, thus the opaque type’s underlying type must be fixed for the scope of the variable.

And in SwiftUI’s case, “some View” means that the body will always be implementing the View protocol, or here means it will always be implementing scene protocol.

any

为了解释 any 解决的问题,我们可以通过一个列子来了解这两个关键字。下面是一个Pizza模型的协议:

protocol Pizza { 
    var size: Int { get } 
    var name: String { get } 
}

在Swift 5.6,你可能会写下面的这种方法,来接收一个Pizza

func receivePizza(_ pizza: Pizza) { 
    print("Omnomnom, that's a nice \(pizza.name)") 
}

当这个函数被调用时,receivePizza 函数接收一个所谓的披萨协议类型,我们可以理解为一个披萨盒子。为了知道这个披萨名称,必须打开这个盒子,也就是获取实现Pizza协议的具体对象,然后获取名称。这意味着 Pizza 几乎没有编译时优化,这使得 receivePizza 方法调用的开销比我们想要的更大。

另外下面的函数,看起来好像是一样的

func receivePizza<T: Pizza>(_ pizza: T) {
    print("Omnomnom, that's a nice \(pizza.name)")
}

不过,这里有一个很主要区别。 Pizza 协议在这里没有用作参数类型。它被用作泛型 T 的约束。这就使得编译器将能够在编译时解析 T 的类型,使得 receivePizza 接受到的是一个具体化的类型。

因为上面这两种方法差异并不是很清楚,所以 Swift 团队引入了 any 关键字。此关键字不添加任何新功能。它迫使我们清楚地传达“这是一种存在主义”:(有点拗口,也不是很好理解,我就把他理解成这么类型的一个东西)

swift

// 上面的第一种写法,增加一个any关键字
func receivePizza(_ pizza: any Pizza) { 
    print("Omnomnom, that's a nice \(pizza.name)") 
}

使用泛型T的示例不需要 any 关键字,因为 Pizza 被用作约束而不是存在类型。

any 关键字由 Swift 5.6 引入,它用来修饰存在类型:一个能够容纳任意遵循某个协议的的具体类型的容器类型。

我们结合下面的代码来理解这段抽象的描述:

protocol P {}
 
struct CP1: P {}
struct CP2: P {}
 
func f1(_ p: any P) -> any P {
  p
}
 
func f2<V: P>(_ p: V) -> V {
  p
}

f1 中的 p 及其返回值都是存在类型,只要是遵循协议 P 的类型实例都是合法的。

f2 中的 p 及其返回值都不是存在类型,而是遵循协议 P 的某个具体类型

在编译期间,f1 中 p 是存在类型(any P),它将 p 底层的具体类型包装在一个“容器”中。而在运行时,从容器中取出内容物才能得知 p 底层的具体类型。p 的类型可被任何遵循协议 P 的某个具体类型进行替换,因此存在类型具有动态分发的特性。

比如下面的代码:

func f3() -> any P {
  Bool.random() ? CP1() : CP2()
}

f3 的返回类型在编译期间是存在类型 any P,但是在运行期间的具体类型是 CP1 或 CP2

而 f2 中的 p 没有被“容器”包装,无需进行装箱、拆箱操作。由于泛型的约束,当我们调用该方法时,就已经确定了它的具体类型。无论是编译期还是运行时,它的类型都是具体的,这又称为静态分发。比如这样调用时:f2(CP1()) ,入参和返回值类型都就已经固化为 CP1,在编译期和运行时都保持为该具体类型。

因为动态分发会带来一定的性能损耗,因此 Swift 引入了 any 关键字来向我们警示存在类型的负面影响,我们应该尽量避免使用它。

上面的示例代码不使用 any 关键字还能通过编译,但从 Swift 6 开始,当我们使用存在类型时,编译器会强制要求使用 any 关键字标记,否则会报错。

在实际开发中,推荐优先使用泛型和 some,尽可能地避免使用 any,除非你真的需要一个动态的类型。