Swift笔记:Swift中的扩展语法
Visit me at CSDN: https://jclee95.blog.csdn.net
My WebSite:http://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.cn/blogs/427655
【介绍】:本文记录Swift中的扩展语法及其使用。
在 Swift 中,扩展(Extension)它允许你为现有的类(Class)、结构体(Struct)、枚举(Enum)以及协议(Protocol)添加新的功能。通过扩展,你可以在不修改原始类型源代码的情况下,为其添加新的方法、计算属性、构造器、下标以及嵌套类型等。这种语法特性使得 Swift 具有很高的灵活性和可扩展性。
扩展的主要目的是向现有类型添加新的功能,而不需要创建该类型的子类或修改其原始实现。这种方式可以使代码更加模块化,提高代码的可读性和可维护性。通过扩展,你可以将相关的功能组织在一起,使类型的定义更加简洁明了。
一般可以通过以下几个方面体现使用扩展语法的用途:
- 添加计算属性:通过扩展,你可以为现有类型添加新的计算属性,包括实例属性和类型属性(静态属性)。这些计算属性可以提供额外的数据或者基于现有属性进行计算。
- 定义方法:扩展允许你为现有类型添加新的实例方法和类型方法(静态方法)。这些方法可以扩展类型的功能,提供额外的操作或行为。
- 提供新的构造器:通过扩展,你可以为现有类型添加新的构造器。对于结构体和枚举,你可以添加新的初始化方法;对于类,你可以添加新的便利构造器,但不能添加新的指定构造器。
- 定义下标:扩展可以为现有类型添加新的下标,使得通过下标语法访问和操作类型的实例更加方便。
- 使类型遵循协议:通过扩展,你可以让现有类型遵循一个或多个协议,并提供协议要求的方法和属性的实现。这种方式可以使类型适配特定的协议,而不需要在原始类型中直接声明遵循。
- 组织和分类相关功能:扩展提供了一种将相关功能组织在一起的方式。你可以根据功能的不同方面或用途,将它们划分到不同的扩展中,提高代码的可读性和可维护性。
扩展和继承都可以用于为现有类型添加新的功能,但它们有以下区别:
- 扩展不需要创建新的子类,而是直接在现有类型上添加功能。相比之下,继承需要创建一个新的子类,并可能需要重写或重新实现某些方法。
- 扩展可以为类、结构体、枚举以及协议添加新的功能,而继承仅适用于类。
- 扩展不能重写或修改现有类型的方法、属性或构造器的实现。如果需要修改现有的实现,应该使用继承并在子类中进行重写。
- 扩展可以在不访问或修改原始类型源代码的情况下添加新的功能,而继承需要访问父类的源代码。
- 当需要创建一个新的类型,并且该类型与现有类型具有"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.min
、stats.max
、stats.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 中添加相应的逻辑。
- 点赞
- 收藏
- 关注作者
评论(0)