Swift笔记:Swift中的扩展语法

举报
jcLee95 发表于 2024/05/19 18:32:37 2024/05/19
【摘要】 本文记录Swift中的扩展语法及其使用。
Swift笔记
Swift中的扩展语法

- 文章信息 -
Author: 李俊才 (jcLee95)
Visit me at CSDN: https://jclee95.blog.csdn.net
My WebSitehttp://thispage.tech/
Email: 291148484@163.com.
Shenzhen China
Address of this article:https://blog.csdn.net/qq_28550263/article/details/139030433
HuaWei: https://bbs.huaweicloud.com/blogs/427655

【介绍】:本文记录Swift中的扩展语法及其使用。

在这里插入图片描述


上一节:《 Swift的面向协议编程  | 下一节:《 暂无 




在 Swift 中,扩展(Extension)它允许你为现有的类(Class)、结构体(Struct)、枚举(Enum)以及协议(Protocol)添加新的功能。通过扩展,你可以在不修改原始类型源代码的情况下,为其添加新的方法、计算属性、构造器、下标以及嵌套类型等。这种语法特性使得 Swift 具有很高的灵活性和可扩展性。

扩展的主要目的是向现有类型添加新的功能,而不需要创建该类型的子类或修改其原始实现。这种方式可以使代码更加模块化,提高代码的可读性和可维护性。通过扩展,你可以将相关的功能组织在一起,使类型的定义更加简洁明了。


一般可以通过以下几个方面体现使用扩展语法的用途:

  1. 添加计算属性:通过扩展,你可以为现有类型添加新的计算属性,包括实例属性和类型属性(静态属性)。这些计算属性可以提供额外的数据或者基于现有属性进行计算。
  2. 定义方法:扩展允许你为现有类型添加新的实例方法和类型方法(静态方法)。这些方法可以扩展类型的功能,提供额外的操作或行为。
  3. 提供新的构造器:通过扩展,你可以为现有类型添加新的构造器。对于结构体和枚举,你可以添加新的初始化方法;对于类,你可以添加新的便利构造器,但不能添加新的指定构造器。
  4. 定义下标:扩展可以为现有类型添加新的下标,使得通过下标语法访问和操作类型的实例更加方便。
  5. 使类型遵循协议:通过扩展,你可以让现有类型遵循一个或多个协议,并提供协议要求的方法和属性的实现。这种方式可以使类型适配特定的协议,而不需要在原始类型中直接声明遵循。
  6. 组织和分类相关功能:扩展提供了一种将相关功能组织在一起的方式。你可以根据功能的不同方面或用途,将它们划分到不同的扩展中,提高代码的可读性和可维护性。

扩展和继承都可以用于为现有类型添加新的功能,但它们有以下区别:

  1. 扩展不需要创建新的子类,而是直接在现有类型上添加功能。相比之下,继承需要创建一个新的子类,并可能需要重写或重新实现某些方法。
  2. 扩展可以为类、结构体、枚举以及协议添加新的功能,而继承仅适用于类。
  3. 扩展不能重写或修改现有类型的方法、属性或构造器的实现。如果需要修改现有的实现,应该使用继承并在子类中进行重写。
  4. 扩展可以在不访问或修改原始类型源代码的情况下添加新的功能,而继承需要访问父类的源代码。
  5. 当需要创建一个新的类型,并且该类型与现有类型具有"is-a"的关系时,应该使用继承。而当需要为现有类型添加新的功能,且不影响原始类型的定义时,应该使用扩展。

总之,扩展提供了一种灵活、非侵入式的方式来扩展现有类型的功能,而无需创建新的子类或修改原始类型的源代码。它可以用于添加计算属性、定义方法、提供新的构造器、定义下标以及使类型遵循协议等。扩展与继承相比,具有更高的灵活性和适用性,可以在不影响原始类型定义的情况下,对类型进行功能扩展。

在实际开发中,可以根据具体的需求和设计来选择使用扩展还是继承。当需要为现有类型添加新的功能,且不影响原始类型的定义时,优先考虑使用扩展。而当需要创建一个新的类型,并且该类型与现有类型具有明确的继承关系时,则可以使用继承。



在 Swift 中,使用 extension 关键字来声明一个扩展。扩展的基本语法如下:

extension SomeType {
    // 在这里添加新的功能
}

其中,SomeType 表示要扩展的现有类型,可以是类、结构体、枚举或协议。在扩展的大括号内,你可以添加新的属性、方法、构造器、下标以及嵌套类型等。

例如,以下代码展示了如何为 Int 类型添加一个新的计算属性 isEven,用于判断一个整数是否为偶数:

extension Int {
    var isEven: Bool {
        return self % 2 == 0
    }
}

现在,你可以在任何 Int 类型的实例上访问 isEven 属性:

let number = 42
print(number.isEven) // 输出 "true"

除了添加新的功能外,扩展还可以用于让现有类型遵循一个或多个协议。通过扩展,你可以为类型提供协议要求的属性和方法的实现。

要使用扩展让类型遵循协议,可以在扩展声明中指定要遵循的协议,并提供协议要求的实现。基本语法如下:

extension SomeType: Protocol1, Protocol2 {
    // 提供协议要求的实现
}

其中,SomeType 表示要扩展的现有类型,Protocol1  Protocol2 表示要遵循的一个或多个协议。

例如,以下代码展示了如何使用扩展让 Int 类型遵循 CustomStringConvertible 协议:

extension Int: CustomStringConvertible {
    var description: String {
        return "The number is \(self)"
    }
}

现在,任何 Int 类型的实例都可以通过 description 属性获取一个自定义的字符串表示:

let number = 42
print(number.description) // 输出 "The number is 42"

通过扩展让类型遵循协议,可以将协议的实现与类型的定义分离,提高代码的可读性和可维护性。这种方式特别适用于为现有类型添加协议遵循,而无需修改类型的原始定义。




你可以使用扩展为现有类型添加新的计算型实例属性。这些属性可以根据实例的其他属性或外部因素计算得出。

例如,以下代码展示了如何为 Double 类型添加一个计算型实例属性 km,用于将双精度浮点数表示的米转换为千米:

extension Double {
    var km: Double {
        return self / 1000.0
    }
}

let distanceInMeters = 5000.0
print(distanceInMeters.km) // 输出 "5.0"

在上述示例中,km 属性通过将 Double 类型的值除以 1000 来计算千米数。现在,你可以在任何 Double 类型的实例上访问 km 属性,以获取对应的千米数。


除了实例属性,你还可以使用扩展为现有类型添加新的计算型类型属性(即静态属性)。类型属性属于类型本身,而不是类型的实例。

例如,以下代码展示了如何为 String 类型添加一个计算型类型属性 defaultGreeting,用于提供一个默认的问候语:

extension String {
    static var defaultGreeting: String {
        return "Hello, World!"
    }
}

print(String.defaultGreeting) // 输出 "Hello, World!"

在上述示例中,defaultGreeting 属性是一个计算型类型属性,它返回一个默认的问候语字符串。你可以通过 String.defaultGreeting 直接访问该属性,而无需创建 String 类型的实例。

通过扩展添加计算型属性,你可以为现有类型提供额外的数据或功能,而无需修改类型的原始定义。这种方式可以提高代码的可读性和可维护性,并将相关的功能组织在一起。


扩展不仅可以添加新的计算属性,还可以为现有类型定义新的方法。通过扩展,你可以为类、结构体、枚举添加新的实例方法和类型方法,以扩展它们的功能。


你可以使用扩展为现有类型添加新的实例方法。这些方法可以对实例进行操作或提供额外的功能。

例如,以下代码展示了如何为 Int 类型添加一个新的实例方法 repeatTask(count:task:),用于重复执行指定的任务多次:

extension Int {
    func repeatTask(count: Int, task: () -> Void) {
        for _ in 0..<count {
            task()
        }
    }
}

3.repeatTask(count: 3) {
    print("Hello!")
}
// 输出:
// Hello!
// Hello!
// Hello!

在上述示例中,repeatTask(count:task:) 方法接受两个参数:count 表示重复执行的次数,task 是一个无参数无返回值的闭包,表示要执行的任务。方法内部使用一个 for 循环重复执行指定次数的任务。

现在,你可以在任何 Int 类型的实例上调用 repeatTask(count:task:) 方法,传递重复次数和要执行的任务闭包,以便重复执行该任务。


除了实例方法,你还可以使用扩展为现有类型添加新的类型方法(即静态方法)。类型方法属于类型本身,而不是类型的实例。

例如,以下代码展示了如何为 Double 类型添加一个新的类型方法 random(min:max:),用于生成指定范围内的随机浮点数:

extension Double {
    static func random(min: Double, max: Double) -> Double {
        return Double.random(in: min...max)
    }
}

let randomNumber = Double.random(min: 1.0, max: 10.0)
print(randomNumber)

输出一个随机生成的数字如:7.3647678901597647

在上述示例中,random(min:max:) 方法是一个类型方法,它使用 Double.random(in:) 函数生成指定范围内的随机浮点数。通过扩展添加这个类型方法,你可以直接通过 Double.random(min:max:) 调用它,而无需创建 Double 类型的实例。


在扩展中,你还可以定义可变实例方法,用于修改实例本身或其属性。对于值类型(如结构体和枚举),你需要在方法声明前添加 mutating 关键字,以表示该方法可以修改实例。

例如,以下代码展示了如何为 Int 类型添加一个可变实例方法 square(),用于将整数进行平方运算:

extension Int {
    mutating func square() {
        self = self * self
    }
}

var number = 5
number.square()
print(number)
// 输出:25

上述示例中,square() 方法被声明为可变实例方法,通过在方法声明前添加 mutating 关键字。该方法将实例自身的值与自身相乘,并将结果赋值给 self,从而修改了实例的值。

当你在一个 Int 类型的变量上调用 square() 方法时,该变量的值会被修改为其平方值。

当你在一个 Int 类型的变量上调用 square() 方法时,该变量的值会被修改为其平方值。


扩展不仅可以添加新的属性和方法,还可以为现有类型提供新的构造器。通过扩展,你可以为值类型(结构体和枚举)添加新的构造器,为类添加新的便利构造器。


对于值类型(结构体和枚举),如果你没有定义任何自定义构造器,Swift 会自动提供一个默认的成员逐一构造器。你可以使用扩展来添加新的构造器,以提供更多的初始化选项。

例如,以下代码定义了一个表示二维坐标点的 Point 结构体:

struct Point {
    var x: Double
    var y: Double
}

你可以使用扩展为 Point 结构体添加一个新的构造器,用于通过极坐标初始化点:

extension Point {
    init(radius: Double, angle: Double) {
        x = radius * cos(angle)
        y = radius * sin(angle)
    }
}

现在,你可以使用新的构造器通过极坐标来创建 Point 实例:

let point = Point(radius: 5.0, angle: .pi / 4)
print(point) // 输出 "Point(x: 3.5355339059327378, y: 3.5355339059327373)"

通过扩展添加新的构造器,你可以为值类型提供更多的初始化方式,使其更加灵活和适应不同的使用场景。


对于类,你可以使用扩展添加新的便利构造器,但不能添加新的指定构造器或析构器。指定构造器和析构器必须始终由类的原始实现提供。

例如,以下代码定义了一个表示人的 Person 类:

class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

你可以使用扩展为 Person 类添加一个新的便利构造器,用于通过生日年份初始化人的年龄:

extension Person {
    convenience init(name: String, birthYear: Int) {
        let currentYear = Calendar.current.component(.year, from: Date())
        let age = currentYear - birthYear
        self.init(name: name, age: age)
    }
}

现在,你可以使用新的便利构造器通过生日年份来创建 Person 实例:

let person = Person(name: "Alice", birthYear: 1990)
print(person.age) // 输出 "33"(假设当前年份为 2023)

通过扩展添加便利构造器,你可以为类提供更多的初始化方式,而无需修改类的原始定义。这种方式可以提高代码的可读性和可维护性,并将相关的初始化逻辑组织在一起。

需要注意的是,在扩展中添加的便利构造器最终必须调用类的指定构造器,以确保实例的完全初始化。


扩展可以为现有类型添加新的下标,使你能够以更便捷的方式访问和操作类型的实例。下标允许你使用中括号 [] 语法来访问实例的特定部分或元素。

扩展可以为现有类型添加新的下标,使你能够以更便捷的方式访问和操作类型的实例。下标允许你使用中括号 [] 语法来访问实例的特定部分或元素。

extension String {
    subscript(index: Int) -> Character {
        return self[self.index(self.startIndex, offsetBy: index)]
    }
}

let message = "Hello, World!"
print(message[7]) // 输出 "W"

在上述示例中,我们为 String 类型添加了一个新的下标,它接受一个 Int 类型的索引参数,并返回字符串中对应索引位置的字符。下标的实现使用了 String 类型的内置方法 index(_:offsetBy:) 来获取指定索引位置的字符。

现在,你可以使用中括号语法 message[7] 来访问字符串 message 中索引为 7 的字符,即 “W”。

现在,你可以使用中括号语法 message[7] 来访问字符串 message 中索引为 7 的字符,即 “W”。

extension Array {
    subscript(range: Range<Int>) -> ArraySlice<Element> {
        get {
            return self[range]
        }
        set {
            self[range] = newValue
        }
    }
}

var numbers = [1, 2, 3, 4, 5]
print(numbers[1...3]) // 输出 "[2, 3, 4]"

numbers[1...3] = [6, 7, 8]
print(numbers) // 输出 "[1, 6, 7, 8, 5]"

在上述示例中,我们为 Array 类型添加了一个新的下标,它接受一个 Range 类型的范围参数,并返回数组中对应范围内的子数组。下标支持读写操作,使用 get 和 set 关键字来定义读取和设置操作。

现在,你可以使用中括号语法 numbers[1…3] 来访问数组 numbers 中索引范围为 1…3 的子数组,即 [2, 3, 4]。同时,你也可以使用下标语法来修改数组中指定范围内的元素,如 numbers[1…3] = [6, 7, 8]。

通过扩展添加下标,你可以为现有类型提供更方便和直观的访问方式,使代码更加简洁和可读。


扩展不仅可以添加新的属性、方法和构造器,还可以在扩展中定义新的嵌套类型,如枚举、结构体和类。嵌套类型可以用于组织和封装与扩展相关的功能和行为。

例如,以下代码展示了如何在 Int 类型的扩展中定义一个新的嵌套枚举 Kind,用于表示整数的类型:

extension Int {
    enum Kind {
        case negative
        case zero
        case positive
    }
    
    var kind: Kind {
        switch self {
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

let number = -5
print(number.kind) // 输出 "negative"

在上述示例中,我们在 Int 类型的扩展中定义了一个新的嵌套枚举 Kind,用于表示整数的类型。Kind 枚举有三个 case:negative、zero 和 positive,分别表示负数、零和正数。

同时,我们还在扩展中添加了一个计算属性 kind,用于根据整数的值返回对应的 Kind 枚举 case。kind 属性使用 switch 语句来判断整数的值,并返回相应的 Kind 枚举 case。

现在,你可以在任何 Int 类型的实例上访问 kind 属性,以获取该整数的类型。例如,number.kind 会返回 .negative,因为 number 的值为 -5。

除了枚举,你还可以在扩展中定义新的结构体和类。这些嵌套类型可以用于封装与扩展相关的数据和行为,提高代码的可读性和可维护性。

例如,以下代码展示了如何在 Array 类型的扩展中定义一个新的嵌套结构体 Statistics,用于计算数组的统计信息:

extension Array where Element: Numeric {
    struct Statistics {
        let min: Element
        let max: Element
        let sum: Element
        let average: Double
    }
    
    var statistics: Statistics {
        guard !isEmpty else {
            fatalError("Cannot compute statistics for an empty array.")
        }
        
        let min = self.min()!
        let max = self.max()!
        let sum = self.reduce(0, +)
        let average = Double(sum) / Double(count)
        
        return Statistics(min: min, max: max, sum: sum, average: average)
    }
}

let numbers = [1, 2, 3, 4, 5]
let stats = numbers.statistics
print(stats.min)     // 输出 "1"
print(stats.max)     // 输出 "5"
print(stats.sum)     // 输出 "15"
print(stats.average) // 输出 "3.0"

在上述示例中,我们在 Array 类型的扩展中定义了一个新的嵌套结构体 Statistics,用于表示数组的统计信息。Statistics 结构体包含了数组的最小值、最大值、总和和平均值。

同时,我们还在扩展中添加了一个计算属性 statistics,用于计算数组的统计信息并返回一个 Statistics 实例。statistics 属性使用 min()max()reduce(_:_:) 等方法来计算数组的最小值、最大值和总和,并根据元素个数计算平均值。

现在,你可以在任何符合 Numeric 协议的数组上访问 statistics 属性,以获取该数组的统计信息。例如,numbers.statistics 会返回一个包含数组统计信息的 Statistics 实例,你可以通过 stats.minstats.maxstats.sum  stats.average 分别访问数组的最小值、最大值、总和和平均值。

通过在扩展中定义嵌套类型,你可以将与扩展相关的数据和行为封装在一个命名空间内,提高代码的可读性和可维护性。嵌套类型可以访问扩展中的其他成员,如属性和方法,使得它们能够更好地协同工作。


扩展不仅可以为现有类型添加新的功能,还可以使类型遵循协议并提供协议所需的默认实现。此外,你还可以使用扩展为协议本身提供默认实现或添加额外功能。


通过扩展,你可以让现有类型遵循一个或多个协议,并提供协议所需的默认实现。这种方式可以在不修改类型原始定义的情况下,使其符合协议的要求。

例如,以下代码定义了一个 Printable 协议,要求遵循者提供一个 printDescription() 方法:

protocol Printable {
    func printDescription()
}

现在,假设我们有一个 Person 结构体,它没有遵循 Printable 协议:

struct Person {
    var name: String
    var age: Int
}

我们可以使用扩展来让 Person 结构体遵循 Printable 协议,并提供 printDescription() 方法的默认实现:

extension Person: Printable {
    func printDescription() {
        print("Name: \(name), Age: \(age)")
    }
}

通过扩展,我们为 Person 结构体添加了 Printable 协议的遵循,并提供了 printDescription() 方法的实现。现在,Person 结构体可以被视为 Printable 类型,并可以调用 printDescription() 方法:

let person = Person(name: "Alice", age: 25)
person.printDescription() // 输出 "Name: Alice, Age: 25"

除了使用扩展让类型遵循协议,你还可以使用扩展为协议本身提供默认实现或添加额外功能。协议扩展允许你为协议中的方法、计算属性或下标提供默认实现,以便所有遵循该协议的类型都可以继承这些实现,以便所有遵循该协议的类型都可以继承这些实现。

例如,以下代码展示了如何使用协议扩展为 Equatable 协议添加一个 isEqual(to:) 方法的默认实现:

extension Equatable {
    func isEqual(to other: Self) -> Bool {
        return self == other
    }
}

在上述示例中,我们使用扩展为 Equatable 协议添加了一个 isEqual(to:) 方法的默认实现。该方法接受一个与当前类型相同的参数 other,并使用 == 运算符比较两个值的相等性。

现在,所有遵循 Equatable 协议的类型都可以继承 isEqual(to:) 方法的默认实现,而无需在每个类型中重复编写该方法:

let x = 5
let y = 5
print(x.isEqual(to: y)) // 输出 "true"

let str1 = "Hello"
let str2 = "Hello"
print(str1.isEqual(to: str2)) // 输出 "true"

在上述示例中,Int  String 类型都遵循了 Equatable 协议,因此它们都继承了 isEqual(to:) 方法的默认实现。我们可以直接在 Int  String 类型的实例上调用 isEqual(to:) 方法,而无需在这些类型中显式实现该方法。

协议扩展的另一个用途是为协议添加额外的功能,如计算属性或便利方法。这些额外的功能可以基于协议的要求来实现,并为遵循协议的类型提供更多的功能。

例如,以下代码展示了如何使用协议扩展为 Collection 协议添加一个 isNotEmpty 计算属性:

extension Collection {
    var isNotEmpty: Bool {
        return !isEmpty
    }
}

在上述示例中,我们使用扩展为 Collection 协议添加了一个 isNotEmpty 计算属性。该属性返回与 isEmpty 属性相反的布尔值,表示集合是否不为空。

现在,所有遵循 Collection 协议的类型都可以使用 isNotEmpty 属性来检查集合是否不为空:

let numbers = [1, 2, 3]
print(numbers.isNotEmpty) // 输出 "true"

let emptyArray: [Int] = []
print(emptyArray.isNotEmpty) // 输出 "false"

通过协议扩展,你可以为协议提供默认实现和额外功能,使得遵循协议的类型能够继承这些实现和功能。这种方式可以提高代码的复用性和可维护性,并为协议提供更多的灵活性和扩展性。


虽然扩展是一个强大的功能,可以为现有类型添加新的功能,但它也有一些限制。以下是扩展的一些主要限制:


扩展不能用于重写类型已有的功能,如存储属性、方法、构造器等。扩展只能添加新的功能,而不能修改或替换现有的实现。

例如,如果一个类型已经定义了一个名为 description 的属性,你不能在扩展中重新定义该属性:

class Person {
    var name: String
    var description: String {
        return "Person named \(name)"
    }
    
    init(name: String) {
        self.name = name
    }
}

extension Person {
    // 编译错误:扩展不能重写类型中已有的 description 属性
    var description: String {
        return "This is a person"
    }
}

在上述示例中,Person 类已经定义了一个名为 description 的计算属性。如果你尝试在扩展中重新定义 description 属性,编译器会报错,因为扩展不能重写类型中已有的功能。


扩展不能为类型添加新的存储属性,只能添加计算属性。这是因为添加新的存储属性会改变类型的内存布局,而扩展不能修改类型的内存布局。

例如,以下代码尝试在扩展中为 Person 类添加一个新的存储属性,但会导致编译错误:

extension Person {
    // 编译错误:扩展不能添加新的存储属性
    var age: Int = 0
}

在上述示例中,我们尝试在 Person 类的扩展中添加一个名为 age 的存储属性,并为其提供默认值 0。但是,编译器会报错,因为扩展不能添加新的存储属性。

如果你需要在扩展中添加属性,可以使用计算属性来替代:

extension Person {
    var age: Int {
        // 根据其他条件计算年龄
        // ...
    }
}

通过使用计算属性,你可以在扩展中提供属性的 getter 和 setter(如果需要),而不会影响类型的内存布局。


扩展不能为现有属性添加属性观察者(willSet 和 didSet)。属性观察者必须在属性定义时就指定,而不能在扩展中添加。

例如,以下代码尝试在扩展中为 Person 类的 name 属性添加属性观察者,但会导致编译错误:

extension Person {
    // 编译错误:扩展不能为现有属性添加属性观察者
    var name: String {
        willSet {
            print("Name will be set to \(newValue)")
        }
        didSet {
            print("Name was set to \(name)")
        }
    }
}

在上述示例中,我们尝试在 Person 类的扩展中为 name 属性添加 willSet 和 didSet 属性观察者。但是,编译器会报错,因为扩展不能为现有属性添加属性观察者。

如果你需要在属性值发生变化时执行一些操作,可以考虑使用计算属性或在属性的 setter 中添加相应的逻辑。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。