the swift programming language-Day2
1.函数
1.1 函数基础
使用func
声明函数,之后是函数名,参数名,最后用->
表明返回值类型
e.g.1
2
3
4func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
函数默认使用参数名作为参数的标识,也可以在参数名之前加标识,或者用_
表明没有标识.
e.g.1
2
3
4func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
返回值可以使用元组(tuple)来返回多个值,使用返回值时可以使用名称,也可以使用数字
e.g.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
如果返回的元组类型可能为空,可以使用可选元组类型作为返回值 .e.g.(Int,Int)?
函数可以嵌套,函数可以把函数作为返回值,函数可以把函数作为参数.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35//嵌套
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
//返回函数
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
//函数参数
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。
1.2 参数标签和参数名称
每个函数参数都有一个参数标签(argument label)以及一个参数名称(parameter name).在调用函数时使用参数标签,在函数中使用参数名称.函数默认把参数名称作为参数标签.
可以使用_
来代替函数标签.
1.3 输入输出参数(In-Out Parameters)
在参数定义前加关键字inout
,这个值被函数修改后,然后被传出函数,将替换原来的值.在调用函数时,需要在输入输出参数前加&
符号,表示这个参数可以被函数修改.
e.g.
1
2
3
4
5
6
7
8
9
10
11 func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印“someInt is now 107, and anotherInt is now 3”
2. 闭包(Closure)
闭包类似于C和OC中的代码块,已经其他语言中的匿名函数(lambda 表达式).闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。
全局和嵌套函数都是特殊的闭包,除此之外,闭包还有一种形式就是闭包表达式.
2.1 闭包表达式
swift标准库有sorted(by:)方法,可以利用闭包表达式对数组进行排序,并返回新数组.以此为例,介绍闭包表达式的用法.
1 | let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] |
sorted(by:)接收一个闭包,排序函数根据该闭包对数组进行排序
第一种提供闭包函数的方法是将一个普通函数作为参数传入
1
2
3
4
5func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]使用闭包表达式的方法如下
1
2
3reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})闭包的函数体部分由关键字
in
引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。根据上下文判断类型
因为排序闭包函数是作为 sorted(by:)
方法的参数传入的,Swift 可以推断其参数和返回值的类型。sorted(by:)
方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool
类型的函数。这意味着 (String, String)
和 Bool
类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->
)和围绕在参数周围的括号也可以被省略:
1 | reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } ) |
单表达式隐式返回
单行表达式闭包可以通过省略
return
关键字来隐式返回单行表达式的结果1
reversedNames = names.sorted(by:{s1,s2 in s1 > s2})
- 参数缩写
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过
$0
,$1
,$2
来顺序调用闭包的参数.1
reversedNames = names.sorted(by: { $0 > $1 } )
运算符方法
Swift 的 String 类型定义了关于大于号(>)
的字符串实现,其作为一个函数接受两个 String 类型的参数并返回Bool
类型的值。而这正好与sorted(by:)
方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现:1
reversedNames = names.sorted(by: >)
尾随闭包
尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用.可以不用写出参数标签
e.g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
reversedNames = names.sorted() { $0 > $1 }2.2 逃逸闭包
闭包是一个引用类型
当一个闭包作为参数传入到函数中,但在函数返回之后才会被执行,这样的闭包就是逃逸闭包,需要在定义时,在参数名之前标注
@escaping
.比如当闭包作为异步操作的函数的参数时,闭包在异步操作之后才会调用,那么这个闭包就需要被标注为”逃逸”.将一个闭包标记为”逃逸”,那么在闭包中必须显示地引用
self
e.g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出“200”
completionHandlers.first?()
print(instance.x)
// 打印出“100”
3.结构体和类
3.1 存储属性
如果创建了一个结构体并将其赋值给一个常量,则无法修改该实例的任何属性.1
2
3
4
5
6
7
8
9struct FixedLengthRange {
var firstValue: Int
let length: Int
}
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数 0,1,2,3
rangeOfFourItems.firstValue = 6
// 尽管 firstValue 是个可变属性,但这里还是会报错
延时加载存储属性
延时加载属性指当第一次被调用的时候才会计算其初始值的属性.在属性声明前使用lazy
来标示一个延时加载存储属性.必须是var
1 | class DataImporter { |
由于使用了 lazy
,DataImporter
的实例 importer
属性只有在第一次被访问的时候才被创建。比如访问它的属性 fileName 时.
全局变量常量都是延迟计算的,不需要lazy
修饰,局部变量则从不延迟计算.
3.2 计算属性
计算属性不直接存储值,而是通过一个getter和可选的setter来间接获取和设置其他属性变量的值.
3.3 属性观察器
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
可以为除了延时加载存储属性之外的其他存储属性添加属性观察器,也可以在子类中通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。
观察器有两种:
1)willSet
在新的值被设置之前调用
2)didSet
在新的值被设置之后调用
e.g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("将 totalSteps 的值设置为 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("增加了 \(totalSteps - oldValue) 步")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步
3.4 类型属性
使用关键字static
定义类型属性.在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内.可以改用关键字 class 来支持子类对父类的实现进行重写。