2024-07-31 17:47:30
,某些文章具有时效性,若有错误或已失效,请在下方留言。可变函数
可变函数是一种不确定迭代的函数,也就是你发送多少参数,它就接受多少参数。
func add(numbers: Int...) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
add(numbers: 1, 2, 3, 4, 5)
运算符重载
运算符重载是一种实现自己的运算符,甚至调整现有运算符(如+
或*
)的能力。
操作符的基础
操作符的重载是使用函数来完成的。
func ==(lhs: MyTypeA, rhs: MyTypeB) -> Bool {
return false
}
if MyTypeA() == MyTypeB() {
print("Match!")
} else {
print("No match!")
}
函数的名称就是运算符本身,即func ==
,因此你要修改的内容一目了然。 该函数希望接收两个类型(lhs
和rhs
,表示左手边和右手边),并返回一个布尔值,报告它们是否相等。
添加到现有运算符
了解了运算符的工作原理,让我们来修改*
运算符,使它能像这样对整数数组进行乘法运算:
func *(lhs: [Int], rhs: [Int]) -> [Int] {
guard lhs.count == rhs.count else { return lhs }
var result = [Int]()
for (index, int) in lhs.enumerated() {
result.append(int * rhs[index])
}
return result
}
因为*
运算符已经存在,所以重要的是lhs
和rhs
参数,这两个参数都是整数数组:当两个整数数组相乘时,这些参数将确保这个新函数被选中。
添加新的运算符
添加新运算符时,需要向 Swift
提供足够的信息才能使用它。
- 必须指定运算符的位置:
prefix(前缀)
、postfix(后缀)
或者infix(中缀)
- 但如果不指定优先级或关联性,Swift 将提供默认值,使其成为低优先级的非关联运算符。
import Foundation
infix operator **
func **(lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
let result = 2 ** 4 // 16
这样的表达方式是行不通的
let result = 4 ** 3 ** 2
let result = 2 ** 3 + 2
这是因为我们使用了默认的优先级和关联性。要解决这个问题,我们需要决定**
与其他运算符相比的优先级。
import Foundation
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentiationPrecedence
func **(lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
let result = 2 ** 3 + 2 // 10
修改现有操作符
precedencegroup RangeFormationPrecedence {
associativity: left
higherThan: CastingPrecedence
}
infix operator ... : RangeFormationPrecedence
func ...(lhs: CountableClosedRange<Int>, rhs: Int) -> [Int] {
let downwards = (rhs ..< lhs.upperBound).reversed()
return Array(lhs) + downwards
}
let range = 1...10...1
print(range)
闭包
闭包是 Swift 中的常见元素:全局函数是闭包,嵌套函数是闭包,函数方法(如sort(
)和map()
)接受闭包,懒属性使用闭包。
简单闭包
let greetPerson = {
print("Hello there!")
}
greetPerson()
由于闭包是一等数据类型,即与整数、字符串和其他数据类型一样,因此可以复制它们,并将它们用作其他函数的参数。
let greetCopy = greetPerson
greetCopy()
在复制闭包时,请记住闭包是一种引用类型–这两个 “副本 “实际上都指向同一个共享闭包。
闭包接受参数,将参数列表写入闭包的大括号中,并在后面加上关键字in
。
let greetPerson = { (name: String) in
print("Hello, \(name)!")
}
greetPerson("Taylor")
如果需要,这里是指定捕获列表的地方。最常用的方法是将self
设为unowned
,以避免强引用循环。
let greetPerson = { [unowned self] (name: String) in
print("Hello, \(name)!")
}
greetPerson("Taylor")
闭包捕获
当两个变量指向同一个闭包时,它们都使用相同的捕获数据。当闭包引用一个值时,它需要确保该值在闭包运行时仍然存在。这个过程被称为捕获,它允许闭包引用和修改它所引用的值,即使原始值已不复存在。
func testCapture() -> () -> Void {
var counter = 0
return {
counter += 1
print("Counter is now \(counter)")
}
}
let greetPerson = testCapture()
greetPerson() // 1
greetPerson() // 2
greetPerson() // 3
let greetCopy = greetPerson
greetCopy() // 4
greetPerson() // 5
greetCopy() // 6
闭包速记语法
let names = ["Michael Jackson", "Taylor Swift", "Michael Caine", "Adele Adkins", "Michael Jordan"]
let result1 = names.filter({ (name: String) -> Bool in
if name.hasPrefix("Michael") {
return true
} else {
return false
}
})
print(result1.count)
Swift 知道传给filter()
的闭包必须接受一个字符串并返回一个布尔值,因此我们可以删除它,只使用一个变量的名称,该变量将用于过滤每个项目。
let result2 = names.filter({ name in
if name.hasPrefix("Michael") {
return true
} else {
return false
}
})
接下来,可以直接返回hasPrefix()
的结果
let result3 = names.filter({ name in
return name.hasPrefix("Michael")
})
尾随闭包允许去掉一组括号
let result4 = names.filter { name in
return name.hasPrefix("Michael")
}
Swift 知道闭包必须返回一个布尔值,而且因为只有一行代码,所以 Swift 知道它必须是返回值的那一行, return
关键字可以省略。
let result5 = names.filter { name in
name.hasPrefix("Michael")
}
当调用闭包时,Swift 会自动创建匿名参数名,由一个美元符号和一个从 0 开始向上的数字组成,比如 $0,$1,$2 …。这些速记参数名与闭包接受的参数相对应。你不能将显式参数和匿名参数混用。
let result6 = names.filter { name in
name.hasPrefix("Michael")
}
let result7 = names.filter {
$0.hasPrefix("Michael")
}
如果使用速记名称,通常会把整个方法调用放在一行中。
let result8 = names.filter { $0.hasPrefix("Michael") }
函数作为闭包
import Foundation
let words = ["1989", "Fearless", "Red"]
let input = "My favorite album is Fearless"
words.contains(where: input.contains)
contains(where:)会对数组中的每个元素都调用一次闭包,直到找到一个返回 true 的元素。将input.contains 传递给它意味着 Swift 将调用 input.contains(”1989″)并返回 false,然后调用input.contains(”Fearless”)并返回 true,然后就此停止。
let numbers = [1, 3, 5, 7, 9]
numbers.reduce(0) { (int1, int2) -> Int in
return int1 + int2
}
代码运行时,将起始值和 1 相加产生 1,然后是 1 和 3(运行总数:4),然后是 4 和 5(9),然后是 9 和 7(16),然后是 16 和 9,总共产生 25。
let numbers = [1, 3, 5, 7, 9]
let result = numbers.reduce(0, +)
+
是一个接受两个整数并返回其和的函数,因此我们可以去掉整个闭包,代之以一个运算符。
逃逸闭包
当闭包传递到函数中时,Swift 默认将其视为非缺省值。这意味着闭包必须立即在函数中使用,不能存储起来留待以后使用。逃逸闭包将会在方法返回后被调用,这些闭包存在于许多需要异步调用闭包的地方。
var queuedClosures: [() -> Void] = []
// 传入的闭包可以在以后使用,在本例中就是在调用executeQueuedClosures()函数时使用。
@MainActor func queueClosure(_ closure: @escaping () -> Void) {
queuedClosures.append(closure)
}
@MainActor func executeQueuedClosures() {
for closure in queuedClosures {
closure()
}
}
executeQueuedClosures()
@autoclosure
调用使用@autoclosure
的函数很常见,但使用它编写函数却不常见。使用该属性时,它会根据你传入的表达式自动创建一个闭包。当你调用一个使用此属性的函数时,你写的代码并不是一个闭包,而是变成了一个闭包。
func printTest(_ result: () -> Void) {
print("Before")
result()
print("After")
}
printTest({ print("Hello") })
这段代码创建了一个printTest()方法,该方法接受一个闭包并调用它。正如你所看到的,print(“Hello”)位于闭包内部,在 “Before “和 “After “之间被调用,因此最终输出是 “Before”、”Hello”、”After”。
如果改用@autoclosure,我们就可以重写printTest()调用,这样就不需要大括号了。
func printTest(_ result: @autoclosure () -> Void) {
print("Before")
result()
print("After")
}
printTest(print("Hello"))
由于使用了@autoclosure,这两段代码产生了完全相同的结果。在第二个代码示例中,print(“Hello”)不会立即执行,因为它被包裹在闭包中,稍后再执行。
~= 运算符
~=
是模式匹配运算符
let range = 1...100
let i = 42
if range ~= i {
print("Match!")
}
不需要运算符,因为可以使用 ranges
的内置contains()
方法进行编码。不过,与contains()
相比,它在语法上确实有一点优势,因为它不需要额外的括号:
let test1 = (1...100).contains(42)
let test2 = 1...100 ~= 42
暂无评论内容