跳到主要内容

如何以及何时使用 Swift 中的扩展

· 阅读需 7 分钟
GoSwiftUI
goswiftui.com

Swift 中的扩展允许你扩展类、结构体、协议和枚举,即使你无法访问源代码。

所谓的追溯建模允许你扩展你没有源代码访问权限的类型。Swift 的这一强大功能使你能够在外部代码之上创建自定义层。作为一名应用程序开发者,何时以及如何使用它们是一项重要的技能,所以让我们深入了解一下。

什么是扩展?

从字面上讲,扩展扩展了现有代码。例如,你可能有一个带有名字和姓氏的 Person 结构体:

    struct Person {
let firstName: String
let lastName: String
}

你可以为这个结构体创建一个扩展并添加一个 fullName 属性:

    extension Person {
var fullName: String {
firstName + " " + lastName
}
}

在这种情况下,我们可以访问 Person 结构体的源代码,因此我们也可以决定在结构体本身内定义 fullName 计算属性:

    struct Person {
let firstName: String
let lastName: String

var fullName: String {
firstName + " " + lastName
}
}

对于这个示例来说,这样做更有意义,因为它将所有可用属性保存在一个地方。

在没有源代码访问权限的情况下扩展类型

当由于缺少源代码访问权限而无法自定义类型的实现时,可以使用扩展来创建自定义访问器。例如,我们可以创建一个 Date 扩展来向当前值添加天数:

    extension Date {
func addingDays(_ days: Int) -> Date {
Calendar.current.date(byAdding: .day, value: days, to: self)!
}
}

我们现在通过添加一个新方法扩展了 Date 类型。我们可以按如下方式使用该方法:

    Date().addingDays(7)

即使我们无法访问 Date 类型的源代码,我们也能够使用自定义功能对其进行扩展。这可以是创建可发现代码的好方法,因为任何扩展方法都可以使用自动完成功能找到:

Swift 中的扩展允许你扩展类型,即使你没有源代码访问权限。

Swift 中的扩展允许你扩展类型,即使你没有源代码访问权限。

通过使你的自定义代码更易于发现,你可以让团队中的其他人发现自定义(通常有用的)扩展。这比创建每个人都应该知道的自定义类型(例如 DateDaysCalculator)效果更好。

使用扩展创建默认协议实现

协议接口不允许你编写可选成员,即使某些方法对于每个实例可能不是必需的。其次,你可能希望提供默认行为并允许类型在需要时覆盖此行为。我们可以使用扩展来创建此行为。

例如,假设有一个 NameContaining 协议:

    protocol NameContaining {
var firstName: String { get }
var lastName: String { get }
var fullName: String { get }
}

我们可以在没有任何更改的情况下使我们的 Person 结构体符合此协议:

    struct Person: NameContaining {
let firstName: String
let lastName: String

var fullName: String {
firstName + " " + lastName
}
}

现在假设我们引入另一个 NameContaining 类型,但这次它是一个 Pet

    struct Pet: NameContaining {
let firstName: String
let lastName: String

var fullName: String {
firstName + " " + lastName
}
}

你可以看到我们必须复制计算的 fullName 属性,即使它的功能与 Person 的实现相匹配。使用协议扩展,我们可以在保持相同功能的同时删除样板代码:

    protocol NameContaining {
var firstName: String { get }
var lastName: String { get }
var fullName: String { get }
}

extension NameContaining {
/// 此计算属性为 `NameContaining` 协议提供默认实现。
var fullName: String {
firstName + " " + lastName
}
}

struct Person: NameContaining {
let firstName: String
let lastName: String
}

struct Pet: NameContaining {
let firstName: String
let lastName: String
}

此技术功能强大,可以防止你编写许多样板代码。如果你确实想提供自定义实现,你始终可以决定覆盖默认实现:

    struct Person: NameContaining {
let firstName: String
let lastName: String

var fullName: String {
let firstNameFirstChar = firstName.first!.uppercased()
return firstNameFirstChar + ". " + lastName
}
}

struct Pet: NameContaining {
let firstName: String
let lastName: String
}

let person = Person(firstName: "Antoine", lastName: "van der Lee")
let pet = Pet(firstName: "Bernie", lastName: "van der Lee")

/// 使用 `fullName` 的自定义实现打印:“A. van der Lee”
print(person.fullName)
/// 使用 `fullName` 的默认实现打印:“Bernie van der Lee”
print(pet.fullName)

使用扩展符合协议

Swift 中的扩展还允许你添加协议一致性。如果你想在源文件外部定义协议一致性,或者在无法访问源代码的情况下,这将非常有用。

例如,我们可以扩展 ContactsUI 框架的 CNContact 类型并使其符合我们的 NameContaining 协议:

    import ContactsUI

extension CNContact: NameContaining {
var firstName: String {
givenName
}
var lastName: String {
middleName + familyName
}
var fullName: String {
givenName + middleName + familyName
}
}

这现在使我们能够创建与 NameContaining 协议一起使用的实例,而无需知道它们是处理 PetPerson 还是 CNContact

    import SwiftUI

struct NameView: View {
let nameContaining: NameContaining

var body: some View {
Text("This person's name is: \(nameContaining.fullName)")
}
}

结论

Swift 中的扩展是一项强大的功能,允许你编写对结构体、类、协议或枚举的扩展。无论你是否可以访问源代码,扩展都可以扩展任何这些类型。将扩展与协议结合使用,你可以编写可重用代码,而无需太多样板。