书籍链接:《Pro Swift》 (链接需要梯子才能打得开)。
一、可变方法
参数类型用类似Int...
表示:
func add(numbers: Int...) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
add(numbers: 1, 2, 3, 4, 5)
二、运算符重载
1. 运算符基础
运算符的重载是通过方法来实现的,例如==
:
func ==(lhs: MyTypeA, rhs: MyTypeB) -> Bool {
return false
}
if MyTypeA() == MyTypeB() {
print("Match!")
} else {
print("No match!")
}
因为在==
中,总是返回false
,所以if-else
总是打印No match!
。lhs
是left-hand-side
的简写,而rhs
是right-hand-side
的简写。
运算符是有优先性和结合性的。例如,一个式子有乘法和加法,我们是先算乘法,后算加法。例如下面这个例子:
let i = 5 * 10 + 1
结果是51
。
下面是Swift源码里面对一些运算符的定义:
precedencegroup AdditionPrecedence {
associativity: left
higherThan: RangeFormationPrecedence
}
precedencegroup MultiplicationPrecedence {
associativity: left
higherThan: AdditionPrecedence
}
infix operator * : MultiplicationPrecedence
infix operator + : AdditionPrecedence
infix operator - : AdditionPrecedence
上面的代码定义了两个类型的优先组,然后定义了相关的运算符。我们可以看到MultiplicationPrecedence
的优先级高于AdditionPrecedence
。三个运算符都被声明为infix
,因为他们都是二元运算符,需要两个操作数。而!
属于prefix
类型,是一元运算符,例如!loggedIn
。
2. 重载运算符
重载*
,实现数组相乘let result = [1, 2, 3] * [1, 2, 3]
,结果为[1, 4, 9]
:
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
}
3. 添加新的运算符
在添加新的运算符时,我们要告诉Swift足够的信息,至少需要指定运算符的位置:前面(prefix
)、后面(postfix
)还是中间(infix
)。另外,如果我们不指定优先级和结合性的话,Swift会默认设置一个低的优先级,并且没有结合性。
添加**
运算符:
我们把它称为指数运算符。例如,2 ** 4 = 2 * 2 * 2 * 2
。我们使用pow()
来实现:
// 首先导入Foundation
import Foundation
// 告诉Swift `**` 是一个 `infix` 运算符,也就是需要两个操作数,运算符在两个数的中间
infix operator **
目前,我们没有指定优先级和结合性,会使用Swift默认的。定义**
方法:
func **(lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
我们尝试运算let result = 2 ** 4
,结果为8
。不错!!!
但是,如果我们要运算let result = 4 ** 3 ** 2
的话,就会报错;而且let result = 2 ** 3 + 2
也会报错。因为我们使用的是默认的优先级和结合性。
下面我们来修复这个问题,根据这个规则:PEMDAS (Parentheses(括号), Exponents(指数), Multiply(乘法), Divide(除法), Add(加法), Subtract(减法)),我们的指数运算符属于Exponents
,优先级高于乘法;至于结合性,我们参考Haskell的做法,先算后面的,例如4 ** 3 ** 2
== 4 ** (3 ** 2)
。完整代码如下:
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentiationPrecedence
func **(lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
let result = 4 ** 3 ** 2 // 262144
三、closure
1. closure的缩写
例如下面的例子,我们想要找出以Michael
开头的名字有多少个,最开始我们会这样写:
let names = ["Michael Jackson", "Taylor Swift", "Michael Caine", "Adele Adkins", "Michael Jordan"]
let result1 = names.filter({ (name: String) -> Bool in
return name.hasPrefix("Michael")
})
print(result1.count)
我们可以看到filter
方法接受一个closure参数,这个closure的类型是(String) -> Bool
。因为Swift知道closure必须是接受一个String
类型的参数,并返回Bool
,所以我们可以把代码简写成:
let result2 = names.filter({ name in
return name.hasPrefix("Michael")
})
因为在closure中只有一行代码,我们还可以把return
去掉:
let result3 = names.filter({ name in
name.hasPrefix("Michael")
})
这还不是最简洁的。当closure被调用的时候,Swift还默认给closure的每一个参数创建了一个匿名的参数,第一个参数对应的匿名参数为$0
, 第二个对应$1
,以此类推。所以,我们可以最终把代码改为:
let result8 = names.filter { $0.hasPrefix("Michael") }
2. 把方法作为closure
我们先看个简单的例子,检查字符串是否包含一个字符串,可以使用contains()
:
let input = "My favorite album is Fearless"
input.contains("album")
同样地,数组也有contains()
:
words.contains { (str) -> Bool in
return true
}
数组还有另外一个contains方法contains(where:)
,接受一个closure参数。我们可以把方法作为closure,传入这个方法:
import Foundation
let words = ["1989", "Fearless", "Red"]
let input = "My favorite album is Fearless"
words.contains(where: input.contains)
最后一行代码在运行时,先把1989
传入closure,将会调用input.contains("1989")
返回false
,然后传入Fearless
,调用input.contains("Fearless")
返回true
,到此停止运行。
我们在看另外一个例子,使用reduce()
求一个数组的和:
let numbers = [1, 3, 5, 7, 9]
numbers.reduce(0) { (int1, int2) -> Int in
return int1 + int2
}
其实我们可以简写成:
let numbers = [1, 3, 5, 7, 9]
let result = numbers.reduce(0, +)
因为+
本身是一个方法,接受两个参数,并返回他们的和,所以我们可以直接用+
代替closure。
3. Escaping closures
我看很多人把Escaping closures翻译为逃逸闭包,那就叫逃逸闭包吧。呵呵!!!
逃逸闭包是指在方法运行完成并返回之后才运行的闭包;而不逃逸闭包是指在方法返回之前必须使用的闭包。
当我们把closure传入方法时,默认情况下是不逃逸闭包。如果我们要使用逃逸闭包,需要用@escaping
关键字:
var queuedClosures: [() -> Void] = []
func queueClosure(_ closure: @escaping () -> Void) {
queuedClosures.append(closure)
}
queueClosure({ print("Running closure 1") })
queueClosure({ print("Running closure 2") })
queueClosure({ print("Running closure 3") })
queuedClosures.forEach { $0() }
4. @autoclosure
@autoclosure
,从他的名称也可以知道,它能将我们传入的代码自动转成closure,例如:
func printTest(_ result: @autoclosure () -> Void) {
print("Before")
result()
print("After")
}
printTest(print("Hello"))
在调用printTest
时,我们只是传入print("Hello")
,在printTest
方法的实现里面,@autoclosure
把我们刚刚传入的代码自动转成一个closure。
四、~=
运算符
我们可以把它理解为是否包含,例如下面的例子:
let range = 1...100
let i = 42
if range ~= i {
print("Match!")
}
// 下面两行代码效果相同
let test1 = (1...100).contains(42)
let test2 = 1...100 ~= 42
完
有任何问题,欢迎大家留言!
欢迎加入我管理的Swift开发群:536353151
,本群只讨论Swift相关内容。
原创文章,转载请注明出处。谢谢!