书籍链接:《Pro Swift》 (链接需要梯子才能打得开)。
一、 final
关键字
如果我们确定方法或者属性不应该被重写,请使用final
标记。这可以让编译器用直接调用替换动态派发调用。
二、 样式匹配
1. switch
// 匹配tuple所有元素
switch authentication {
case ("bilbo", "bagg1n5"):
print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es"):
print("Hello, Paul Hudson!")
default:
print("Who are you?")
}
// 匹配tuple的部分元素,使用`_`代替我们不关心的元素
let authentication = (name: "twostraws", password: "fr0st1es",
ipAddress: "127.0.0.1")
switch authentication {
case ("bilbo", "bagg1n5", _):
print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es", _):
print("Hello, Paul Hudson!")
default:
print("Who are you?")
}
在匹配过程中,Swift只会执行第一个符合条件的case。例如下面这个例子,第一个case符合任何情况,只会执行第一个case。
switch authentication {
case (_, _, _):
print("You could be anybody!")
case ("bilbo", "bagg1n5", _):
print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es", _):
print("Hello, Paul Hudson!")
default:
print("Who are you?")
}
另外我们还可以使用let
来获取tuple的元素值:
switch authentication {
case ("bilbo", "bagg1n5", _):
print("Hello, Bilbo Baggins!")
case ("twostraws", let password, _):
print("Hello, Paul Hudson: your password was \(password)!")
default:
print("Who are you?")
}
同时我们还可以传入一个动态计算的tuple:
func fizzbuzz(number: Int) -> String {
switch (number % 3 == 0, number % 5 == 0) {
case (true, false):
return "Fizz"
case (false, true):
return "Buzz"
case (true, true):
return "FizzBuzz"
case (false, false):
return String(number)
}
}
print(fizzbuzz(number: 15))
2. 循环
let twostraws = (name: "twostraws", password: "fr0st1es")
let bilbo = (name: "bilbo", password: "bagg1n5")
let taylor = (name: "taylor", password: "fr0st1es")
let users = [twostraws, bilbo, Taylor]
for case ("twostraws", "fr0st1es") in users {
print("User twostraws has the password fr0st1es")
}
使用let得到当前user的name和password:
for case (let name, let password) in users {
print("User \(name) has the password \(password)")
}
// 还可以简写成这样:
for case let (name, password) in users {
print("User \(name) has the password \(password)")
}
还可以和case
结合起来:
for case let (name, "fr0st1es") in users {
print("User \(name) has the password \"fr0st1es\"")
}
只有密码是fr0st1es
的元素,才会执行里面的语句。
3. 匹配可选类型
使用.some
和.none
来匹配有值和没有值:
let name: String? = "twostraws"
let password: String? = "fr0st1es"
switch (name, password) {
case let (.some(name), .some(password)):
print("Hello, \(name)") // 这里的name不是第一行的变量name,而是已经被`.some()`unwrapped了的name
case let (.some(name), .none):
print("Please enter a password.")
default:
print("Who are you?")
}
使用问号?
switch (name, password) {
case let (name?, password?):
print("Hello, \(name)")
case let (username?, nil):
print("Please enter a password.")
default:
print("Who are you?")
}
这个效果跟第一个例子一样,.some()
等同于?
,.none
等同于nil
。
.some
和?
同样也可以在for-case-let
中使用:
let data: [Any?] = ["Bill", nil, 69, "Ted"]
for case let .some(datum) in data {
print(datum)
}
for case let datum? in data {
print(datum)
}
4. 匹配范围
使用switch
:
let age = 36
switch age {
case 0 ..< 18:
print("You have the energy and time, but not the money")
case 18 ..< 70:
print("You have the energy and money, but not the time")
default:
print("You have the time and money, but not the energy")
}
使用if-else
:
if case 0 ..< 18 = age {
print("You have the energy and time, but not the money")
} else if case 18 ..< 70 = age {
print("You have the energy and money, but not the time")
} else {
print("You have the time and money, but not the energy")
}
我们还可以用~=
把if-case
改写成:
if 0 ..< 18 ~= age {
print("You have the energy and time, but not the money")
} else if 18 ..< 70 ~= age {
print("You have the energy and money, but not the time")
} else {
print("You have the time and money, but not the energy")
}
再者,0 ..< 18
其实是一个Range
实例,可以调用contains()
方法,改为这样可以更容易理解:
if (0 ..< 18).contains(age) {
print("You have the energy and time, but not the money")
} else if (18 ..< 70).contains(age) {
print("You have the energy and money, but not the time")
} else {
print("You have the time and money, but not the energy")
}
把之前提到的例子结合起来:
let user = (name: "twostraws", password: "fr0st1es", age: 36)
switch user {
case let (name, _, 0 ..< 18):
print("\(name) has the energy and time, but no money")
case let (name, _, 18 ..< 70):
print("\(name) has the money and energy, but no time")
case let (name, _, _):
print("\(name) has the time and money, but no energy")
}
5. 匹配枚举及其关联值
匹配枚举及其关联值:
enum WeatherType {
case cloudy(coverage: Int)
case sunny
case windy
}
let today = WeatherType.cloudy(coverage: 100)
switch today {
case .cloudy(let coverage):
print("It's cloudy with \(coverage)% coverage")
case .windy:
print("It's windy")
default:
print("It's sunny")
}
使用where
来匹配关联值的范围:
switch today {
case .cloudy(let coverage) where coverage < 100:
print("It's cloudy with \(coverage)% coverage")
case .cloudy(let coverage) where coverage == 100:
print("You must live in the UK")
case .windy:
print("It's windy")
default:
print("It's sunny")
}
还可以用Range
来匹配范围:
switch today {
case .cloudy(let coverage) where coverage == 0:
print("You must live in Death Valley")
case .cloudy(let coverage) where (1...50).contains(coverage):
print("It's a bit cloudy, with \(coverage)% coverage")
case .cloudy(let coverage) where (51...99).contains(coverage):
print("It's very cloudy, with \(coverage)% coverage")
case .cloudy(let coverage) where coverage == 100:
print("You must live in the UK")
case .windy:
print("It's windy")
default:
print("It's sunny")
}
在for循环中匹配枚举:
let forecast: [WeatherType] = [.cloudy(coverage: 40), .sunny, .windy, .cloudy(coverage: 100), .sunny]
for case let .cloudy(coverage) in forecast {
print("It's cloudy with \(coverage)% coverage")
}
// 或者
let forecast: [WeatherType] = [.cloudy(coverage: 40), .sunny, .windy, .cloudy(coverage: 100), .sunny]
for case .cloudy(40) in forecast {
print("It's cloudy with 40% coverage")
www.hackingwithswift.com 23
}
6. 匹配类型
使用is
进行类型匹配:
let view: AnyObject = UIButton()
switch view {
case is UIButton:
print("Found a button")
case is UILabel:
print("Found a label")
case is UISwitch:
print("Found a switch")
case is UIView:
print("Found a view")
default:
print("Found something else")
}
在一个view数组中查找某个类型的view:
for label in view.subviews where label is UILabel {
print("Found a label with frame \(label.frame)")
}
上面这个例子没有把label
转换成UILabel
,我们可以使用下面这种方式:
for case let label as UILabel in view.subviews {
print("Found a label with text \(label.text)")
}
三、 where
关键字的使用
where
后面可以接条件语句:
let celebrities: [String?] = ["Michael Jackson", nil, "Michael
Caine", nil, "Michael Jordan"]
for name in celebrities where name != nil {
print(name)
}
// 结果
Optional("Michael Jackson")
Optional("Michael Caine")
Optional("Michael Jordan")
// 可以使用下面这种形式直接在for语句中unwrap:
for case let name? in celebrities {
print(name)
}
// 结果
Michael Jackson
Michael Caine
Michael Jordan
四、 nil
的处理
使用??
来unwrapp一个可选类型:
let name: String? = "Taylor"
let unwrappedName = name ?? "Anonymous" // 如果name是nil,则unwrappedName为Anonymous;如果不为nil,则unwrappedName为name被unwrapped后的值
print(unwrappedName)
使用try?
对可能抛出错误进行处理:
//如果使用do-catch
let savedText: String
do {
savedText = try String(contentsOfFile: "saved.txt")
} catch {
print("Failed to load saved text.")
savedText = "Hello, world!"
}
//使用try?可以让代码更简洁
let savedText = (try? String(contentsOfFile: "saved.txt")) ??
"Hello, world!"
print(savedText)
五、 guard
的使用
guard
一般用于要确保满足一定的条件下才能继续向下执行的情况:
func giveAward(to name: String) {
guard name == "Taylor Swift" else { // 这个guard语句我们可以这么读:确保name是"Taylor Swift",否则返回
print("No way!")
return
}
print("Congratulations, \(name)!")
}
// 如果使用if-else,代码没那么简洁
func giveAward(to name: String) -> String {
let message: String
if name == "Taylor Swift" {
message = "Congratulations, \(name)!"
} else {
message = "No way!"
}
return message
}
很明显,使用guard
会简洁易懂。
使用guard
来unwrap可选类型:
func giveAward(to name: String?) {
guard let winner = name else {
print("No one won the award")
return
}
print("Congratulations, \(winner)!")
}
六、 lazy
的使用
lazy
修饰的代码,只有在用到的时候才会被执行,并且只执行一次。
- 使用closure和lazy初始化属性:
lazy var yourVariableName: SomeType = {
return SomeType(whatever: "foobar")
}()
这种写法不仅可以提高应用的性能,而且把初始化的相关代码都组织在了一起,这样代码不会很乱。如果在closure里面使用了self
,也不会造成循环引用的问题,我们可以放心使用。注意:如果这个属性没有被lazy
修饰,在closure里面是不能使用self
的,因为这时self
还没有初始化完成,不能调用self
。
如果我们觉得用closure的形式初始化属性把属性和closure放在了一起,属性看起来不够简洁,我们可以使用下面这种方式:
class Singer {
let name: String
init(name: String) {
self.name = name
}
lazy var reversedName: String = self.getReversedName()
private func getReversedName() -> String {
return "\(name.uppercased()) backwards is \(String(name.uppercased().reversed()))!"
}
}
-
lazy
在序列中的应用
举个例子,我们写一个函数来计算斐波那契数列中某个位置的数字是多少。什么是斐波那契数列?简单的说就是后面一个数是前面两个数的和,大家可以点击查看。
函数如下图,fibonacci(of num: Int)
是求某个位置的数字的函数,fibonacciSequence
是斐波那契数列中第0到21的数字组成的数组,然后打印第11个数字。
红色框的两个数字的和,就是运行下面两行代码时fibonacci(of num: Int)
的总运行次数。
let fibonacciSequence = (0...20).map(fibonacci)
print(fibonacciSequence[10])
如果我们使用了lazy
,只需要(89 + 88)次,可说是大大提高了性能。
使用了lazy
之后,当我们执行let lazyFibonacciSequence = Array(0...199).lazy.map(fibonacci)
时,实际上是没有进行运算的,而是调用了print(lazyFibonacciSequence[10])
之后,才会进行真正的运算,并且只会计算第11个数。
但是我们要注意的一点时,在序列中使用lazy
时,不会有记忆的功能,也就是说,我们重复调用print(lazyFibonacciSequence[10])
时,fibonacci(of num: Int)
的总运行次数会翻倍。
七、 解构 (Destructing)
何为解构?熟悉JavaScript的同学应该知道这个词的含义。在JavaScript中,如果一个Person
类有name
和age
两个属性,我们可以用解构来获取Person
实例的属性:
const { name, age } = lebronJames;
类似地,在Swift中,可用解构来获取tuple中的值:
let data = ("one", "two", "three")
let (one, two, three) = data
如果我们不想要其中的某个值,用_
代替:
let (_, two, three) = data
tuple的解构在交换两个值的时候非常有用。在面试中,有可能出现过这样的问题:在不使用第三个变量的情况下,如果交换两个数字?
第一种方法:
var a = 10
var b = 20
a=a+b
b=a-b
a=a-b
print(a)
print(b)
第二种方法,在Swift中,利用tuple解构一行代码搞定:
(b, a) = (a, b)
八、 标签
在Swift中,我们可以给for循环或者if语句加标签。
1. for循环
举个例子,我们要在一个二维字符串数组中找到一个字符串x
,我们可能会使用下面的代码:
// 初始化二维数组
var board = [[String]](repeating: [String](repeating: "", count: 10), count: 5)
// 把board[3][5]设置为我们要找的字符串`x`
board[3][5] = "x"
for (rowIndex, cols) in board.enumerated() {
for (colIndex, col) in cols.enumerated() {
if col == "x" {
print("Found the `x` at row \(rowIndex) col \(colIndex)!")
}
}
}
我们都知道,上面的代码不够好,因为当找到x
后,两个for循环还会继续运行,我们可以加个break
:
for (rowIndex, cols) in board.enumerated() {
for (colIndex, col) in cols.enumerated() {
if col == "x" {
print("Found the `x` at row \(rowIndex) col \(colIndex)!")
break
}
}
}
但是代码还是有问题,找到x
之后,里面的循环停止,但是外面的循环还是会继续执行。通常的做法,我们会想到加个临时变量,记录是否找到了x
,将代码改为:
var hasFoundX = false
for (rowIndex, cols) in board.enumerated() {
guard !hasFoundX else { break }
for (colIndex, col) in cols.enumerated() {
if col == "x" {
print("Found the `x` at row \(rowIndex) col \(colIndex)!")
hasFoundX = true
break
}
}
}
当我们使用Swift的标签特性之后,代码变得简单很多:
rowLoop: for (rowIndex, cols) in board.enumerated() {
for (colIndex, col) in cols.enumerated() {
if col == "x" {
print("Found the treasure at row \(rowIndex) col \
(colIndex)!")
break rowLoop
}
}
}
2. if语句
有这么一个例子,需要满足很多个条件之后,才能运行print("Printed successfully!")
:
if userRequestedPrint() {
if documentSaved() {
if userAuthenticated() {
if connectToNetwork() {
if uploadDocument("resignation.doc") {
if printDocument() {
print("Printed successfully!")
}
} }
} }
}
上面的代码非常难看,使用了标签之后,清晰易懂:
printing: if userRequestedPrint() {
if !documentSaved() { break printing }
if !userAuthenticated() { break printing }
if !connectToNetwork() { break printing }
www.hackingwithswift.com 48
if !uploadDocument("work.doc") { break printing }
if !printDocument() { break printing }
print("Printed successfully!")
}
// 我们还可以用guard
printing: if userRequestedPrint() {
guard documentSaved() else { break printing }
guard userAuthenticated() else { break printing }
guard connectToNetwork() else { break printing }
guard uploadDocument("work.doc") else { break printing }
guard printDocument() else { break printing }
print("Printed successfully!")
}
对于上面两种写法,我个人更喜欢用guard
这种形式,因为guard
后面接的是肯定的语句,而if
后面接的是取反的语句,没那么好理解。
完
有任何问题,欢迎大家留言!
欢迎加入我管理的Swift开发群:536353151
,本群只讨论Swift相关内容。
原创文章,转载请注明出处。谢谢!