04、函数
1、函数基础
1.1 定义一个最简单的函数
func printHello(){
print("Hello")
}
其中:
func是定义函数的关键字,后面是函数名;
()中是可选的参数列表,现在是空参数
()后面是函数的返回值,现在是没有返回值
{}中是函数要封装的逻辑
1.2 设置参数
func pringHello(to somebody:String){
print("Hello\(somebody)")
}
printHello(to:"Lichangan")
在Swift里,函数的参数实际上有两个名字,一个叫做internal name,表示在函数内部使用;一个叫做external name,表示在函数外部使用。
to:就是external name
somebody:就是internal name
默认情况下,如果不特别定义external name,它和internal name则是相等的。
如果你不需要在函数调用的时候,使用external name,就要在定义函数的时候,在external name的位置,明确使用_表示忽略:
func pringHello(_ somebody:String){
print("Hello\(somebody)")
}
pringHello("LiChangan")
在Swift 3里,函数的第一个参数不再默认缺省忽略external name,它和函数的其他参数是一样的。
1.3 为参数设置默认值
func printHello(_ somebody:String = "World!"){
print("Hello\(somebody)")
}
printHello("LiChangan")
printHello()
1.4 定义可变长参数
func mul(_ numbers: Int ...) {
let arrayMul = numbers.reduce(1, *)
print("mul: \(arrayMul)")
}
用numbers: Int...的形式,表示函数可以接受的Int参数的个数是可变的。实际上,numbers的类型,是一个Array<Int>.
mul(1,2,3,4)
1.5 定义inout参数
在Swift里,函数的参数有一个性质:默认情况下,参数是只读的,这也就意味着:
你不能在函数内部修改参数值;
你也不能通过函数参数对外返回值;
func mul(result: Int, _ numbers: Int ...) {
result = numbers.reduce(1, *) // !!! Error here !!!
print("mul: \(result)")
}
直接在函数内部修改参数的值会报错,函数的参数默认是个常量,因此编译器会提示你不能在函数内部对常量赋值。
如果我们希望参数可以被修改,并且把修改过的结果返回给传递进来的参数,我们需要用inout关键字修饰一下参数的类型,明确告诉Swift编译器我们要修改这个参数的值。
func mul(result: inout Int, _ numbers: Int ...) {
result = numbers.reduce(1, *)
print("mul: \(result)")
}
var result = 0
mul(result: &result, 1,2,3,4,5,6)
对于inout类型的参数,我们在调用函数的时候,也需要在参数前明确使用&.
上面我们通过参数result获取到了mul函数的计算结果,通过参数来获取返回值只能算函数的某种副作用,更正规的做法是通过返回值获取参数。
1.6 通过函数返回内容
func mul(_ numbers: Int ...)->Int {
let result = numbers.reduce(1, *)
return result
}
let result = mul(1,2,3,4,5,6)
1.7 函数类型
对于一个函数来说,把它的参数和返回值放在一起,就形成了它的签名,这是用于描述函数自身属性的一种类型。
func mul(m: Int, of n: Int) -> Int {
return m * n
}
上面的类型为(Int,Int)->Int,函数名称和参数名称都不是函数类型的一个部分
let fnMul = mul
fnMul(m:2,n:3)会报错,因为函数类型为(Int,Int)->Int
正确的调用方法:
fnMul(2,3)
函数这种类型,和Swift其它类型有着完全相同的语法功能。它们主要包括:
(1)可以用来定义变量,例如我们之前定义的fnMul和fnDiv,这种类型的变量同样可以当作函数来调用;
(2)可以当成函数参数;
(3)可以被函数返回;
1.8 一等公民
之所以把函数叫做一等公民,是因为
- 函数可以用来定义变量
- 函数可以当做参数
- 函数可以当做返回值
typealias Transform<T> = (_ el:Int) -> T
....
public func map<T>(arr:[Int],_ transform: Transform<T>) -> [T] {
var newArr = [T]()
for element in arr {
let newElement = transform(element)
newArr.append(newElement)
}
return newArr
}
2、 闭包
2.1 闭包的概念
闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用。因此被称为“闭包”,Swift 能够为你处理所有关于捕获的内存管理的操作。
2.2 闭包表达式
语法:
{(parameters)->(return type) in
statements
}
closure expression就是函数的一种简写形式。
函数的写法:
func square(n:Int)->Int{
return n*n
}
闭包表达式的写法:
let squareExpression = { (n:Int)->Int in
return n*n
}
square(n: 2) //函数调用方式
squareExpression(2) //闭包表达式调用方法
它们都可以当做参数来使用
let numbers = [1, 2, 3, 4, 5]
numbers.map(square) // [1, 4, 9, 16, 25]
numbers.map(squareExpressions) // [1, 4, 9, 16, 25]
numbers.map({ (n: Int) -> Int in
return n * n
})
首先,Swift可以根据numbers的类型,自动推导出map中的函数参数以及返回值的类型,因此,我们可以在closure expression中去掉它:
numbers.map({ n in return n * n })
其次,如果closure expression中只有一条语句,Swift可以自动把这个语句的值作为整个expression的值返回,因此,我们还可以去掉return关键字:
numbers.map({ n in n * n })
第三,如果你觉得在closure expression中为参数起名字是个意义不大的事情,我们还可以使用Swift内置的$0/1/2/3/4这样的形式作为closure expression的参数替代符,这样,我们连参数声明和in关键字也都可以省略了:
numbers.map({ $0 * $0 })
第四,如果函数类型的参数在参数列表的最后一个,我们还可以把closure expression写在()外面,让它和其它普通参数更明显的区分开:
numbers.map() { $0 * $0 }
最后,如果函数只有一个函数类型的参数,我们甚至可以在调用的时候,去掉():
numbers.map { $0 * $0 }
2.3 尾随闭包
如果你需要将一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾
随闭包将增强函数的可读性。尾随闭包是一个被书写在函数形式参数的括号外面(后
面)的闭包表达式。
2.4 捕获值
一个函数加上它捕获的变量一起,才算一个闭包。
一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作
用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
func makeCounter() -> () -> Int {
var value = 0
return {
value += 1
return value
}
}
let counter1 = makeCounter()
let counter2 = makeCounter()
(0...2).forEach { _ in print(counter1()) } // 1 2 3
(0...5).forEach { _ in print(counter2()) } // 1 2 3 4 5 6
首先,尽管从makeCounter返回后,value已经离开了它的作用域,但我们多次调用counter1或counter2时,value的值还是各自进行了累加。这就说明,makeCounter返回的函数,捕获了makeCounter的内部变量value。
此时,counter1和counter2就叫做closure,它们既有要执行的逻辑(把value加1),还带有其执行的上下文(捕获的value变量)。
其次,counter1和counter2分别有其各自捕获的value,也就是其各自的上下文环境,它们并不共享任何内容。
函数同样可以是一个Closure,
func makeCounter() -> () -> Int {
var value = 0
func increment() -> Int {
value += 1
return value
}
return increment
}
2.5闭包是引用类型
在 Swift 中,函数和闭包都是引用类型。
无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是将常量和变量
设置为对函数和闭包的引用。
2.6 逃逸闭包
当把一个closure用作函数参数时,默认都是non escaping属性的,也就是说,它们只负责执行逻辑,但不会被外界保存和使用。
一旦closure有机会逃逸到函数作用域外部,使用@escaping来修饰它。一旦逃逸就有可能产生循环引用,就需要处理因为闭包产生的循环引用。
作为函数参数的closure默认是non escaping属性的。但在下面这两种情况里,closure默认是escaping属性的:
首先,所有自定义类型的closure属性,默认是escaping的。
fav.buttonPressed = { [weak counter] index in
counter?.buttonPressed(at: index)
}
其次,如果closure被封装在一个optional里,它默认是escaping的,
func calc(_ n: Int, by: ((Int) -> Int)?) -> Int {
guard let by = by else { return n }
return by(n)
}
2.7 自动闭包
自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包。它不
接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。
自动闭包允许你延迟处理,因此闭包内部的代码直到你调用它的时候才会运行。对于
有副作用或者占用资源的代码来说很有用,因为它可以允许你控制代码何时才进行求
值。
通过 @autoclosure 标志标记它的形式参数使用了自动闭包。现在你可以调用函数就
像它接收了一个 String 实际参数而不是闭包。实际参数自动地转换为闭包,因为
customerProvider 形式参数的类型被标记为 @autoclosure 标记。
2.8 自动闭包+逃逸闭包
如果你想要自动闭包允许逃逸,就同时使用 @autoclosure 和 @escaping 标志。