swift-函数和闭包

470 阅读12分钟

继续吧,希望每天都有一点进步。

函数

在 Swift 中,每个函数都有一个由函数的参数值类型和返回值类型组成的类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。

函数类型:函数的参数和返回数据的类型都相同的函数,为同一类型的函数。忽略参数名称的函数,可以作变量值、参数、返回值。

参数和返回值

每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作实参)。函数的实参必须与函数参数表里参数的顺序一致。

//无返回值 无参数
func test() {
    print("test()")
}
//调用
test()

//和test一样 无返回值 void可以不写
func test2() -> Void {
}

func test3() -> Int {
    return 100
}

//参数名称name 类型String
func test4(name:String) -> String {
    return name
}
print(test4(name:"hello world"))

//参数入参 返回值 什么类型都可以 元组 函数都可以

//可变参数
func test5(age:Int...) {
    print(type(of: age))//Array<Int>

    for item in age {
        print(item)
    }
}
test5(age: 3,6,9)
//用元组(tuple)类型让多个值作为一个复合值从函数中返回
func test6(name:(n1:String,n2:Int)) -> (String,Int){
    var value:(String,Int)
    value.0 = name.n1 + "balabala"
    value.1 = name.n2 + 20
    return value
}
var test6Value = test6(name: (n1: "hh", n2: 5))
print(test6Value)

如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用可选的 元组返回类型反映整个元组可以是 nil的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 (Int, Int)? 或 (String, Int, Bool)?

参数名称

//outname 外部调用名称  inname 函数内部使用名称 两个名称顺序不能反  不能颠倒使用
func test(outname inname:String){
    print(inname)
}
test(outname: "hello")

//只写一个参数名 是外部名称 默认 内部名称和外部名称相同
func test1(name:String) {
    print(name)
}

test1(name: "hello")

// _ 表示外部名称忽略  内部名称不能忽略
func test2(_ inname:String) {
    print(inname)
}
test2("swift")

func test3(outn1 inn1:String,outn2 inn2:Int) {
    print(inn1)
    inn2 + 20
}
test3(outn1: "hello", outn2: 20)

在函数调用的时候,每一个参数都需要传值嘛?其实并不是,准确来说,每一个参数都必须有值,但不一定是调用时传入,Swift语言中函数的参数,支持设置默认值,如果对某个参数设置了默认值,调用时对于这个参数就可传可不传。

func myFunc(param1: Int, param2: Int = 2, param3: Int = 1) {

    param3 + param2 + param1

}

myFunc(param1: 3)

还有一种特殊情况,开发者需要写参数个数不定的函数,例如print()函数,入参数量就是不确定的。在某个参数类型后面追加符号“...”,就会将此参数设置为数量可变。在函数内部,参数值会被包装成一个集合类型,需要注意的是参数类型必须相同,并且可以传递多组数量可变的参数,不同参数也可以是不同类型。

func myFunc2(params1: Int... , params2: String...) {

    var sum = 0;

    for count in params1 {

        sum += count

    }

    for temp in params2 {

        print(temp)

    }

}

myFunc2(params1: 1,2,3,4, params2: "h","hh","jjj")

assert断言

func play(param:Int) {
    if param < 10 {
        assert(false, "stop run")
    }
    print("this is " + String(param))
}
play(param: 2)

guard

func test1(param:Int) -> Int{
    guard param < 10 else {//不满足param < 10 才进入else中
        print("enter guard")
        return 11
    }
    //满足param < 10 才往下走
    print("guard ")
    return 9
}
test1(param: 33)

//test1与test2的功能相反
func test2(param:Int) -> Int {
    if param < 10 {
        return 11
    } else {
        return 9
    }
}


var a:String? = "swift" //改成nil测试一下

func test3(param:String?) {
    guard let value = param else {
        print("enter guard else")
        return
    }
    //guard 定义的常量value 这里可以使用
    print(value)
}

test3(param: a)

//test4 test3功能等同
func test4(param:String?) {
    if let value = param {
        print(value)
    } else {
        print("enter else")
        return
    }
}
test4(param: a)

inout

Swift语言的参数传递,还有一个特点:传递的如果是值类型的参数,那么参数值在传递进函数内部时会将原值复制为一份常量,所以在函数内部不可以修改。而基础数据类型都属于值类型。如果开发者在函数内部修改参数的值,编译器会直接报错。

如果开发中真的需要在函数内部修改参数的值,可以将此参数声明为inout类型,在传参时使用“&”符号,将传递参数变量的内存地址。

func test(param: inout Int){//参数 地址传递 函数内部可以改变其值
    param = param * 2
    print(param)
}
var a = 20
//这里注意加&
test(param: &a)

函数类型

func test2(param:Int){
    print(param)
}

//定义函数类型 Void 不可以忽略
var b:() -> Void
var c:(Int,String) -> String
let d:([Int]) -> (Int,String)


func test3() -> Void
{
}

var e:() -> Void = test3
e()//调用

匿名函数

var f:() -> Void = {() -> Void in
    print("匿名函数")
}
f()

func test4(param1:String,param2:Int) -> String {
    return param1 + String(param2)
}
var g:(String,Int) -> String = test4
g("hello",20)


var h:(String,Int) -> String = {(param1:String,param2:Int) -> String in
    return param1 + String(param2)
}
h("world", 30)

func test5(param:[Int]) -> String {
    var temp:String = ""
    
    for item in param {
        temp = temp + String(item)
    }
    return temp
}
var k:([Int]) -> String = test5
var valeu = k([1,2,3])
print(valeu)

var x:([Int]) -> String = {(array:[Int]) -> String in
    var temp:String = ""
    
    for item in array {
        temp = temp + String(item)
    }
    return temp
}

print(x([3,4,5]))

var y:([Int]) -> String  = x
print(y([13,14,15]))

函数类型作为函数参数

func test() -> Void {
    print("test()函数 无入参无返回值")
}

var a:() -> Void = test
a()

func test1(param:()->Void) {
    param()
}

test1(param: a)

匿名函数 作为入参

test1(param: {() -> Void in
    print("匿名函数 无入参无返回值")
})

print("----------------------")

func test2 (param:(Int,Int) -> Int) -> Int {
    let value = param(1,2)
    print("value = \(value)")
    return value
}

func sum(a:Int,b:Int) -> Int {
    return a + b
}
test2(param: sum)

//这里匿名函数 a  b 参数类型可省略 会自动类型推断
var value = test2(param: {
    (a,b) -> Int in
    return a + b
})
print(value)


var array = [1,3,5,0,2,8]
//倒序
func sortFunc(a:Int, b:Int) -> Bool {
    if (a > b) {
        return true
    } else {
        return false
    }
}

//给sort传入一个匿名函数
array.sort(by: {(a,b) -> Bool in
    if (a < b) {
        return true
    } else {
        return false
    }
})
print(array)

array.sort(by: sortFunc)

函数作为返回值

func play1(a:Int) -> Int {
    return a * a
}

func play2(a:Int) -> Int {
    return a + a
}

func test(param:Bool) -> (Int) -> Int {
    return param ? play1 : play2
}

var a = test(param: true)
print(type(of: a))//(Int) -> Int
print(a(3))

内嵌函数

func test1(param:Bool) -> (Int) -> Int {
    func play1(a:Int) -> Int {
        return a * a
    }

    func play2(a:Int) -> Int {
        return a + a
    }
    return param ? play1 : play2
}
var b = test1(param: false)
b(5)

匿名函数的几种简写

var a:() -> Void = {
    print("a")
}
print(type(of: a)) //() -> ()
a()

var b = {}
print(type(of: b))//() -> ()

print("-------------------------------------")

func test(param:() -> Void) {
    param()
}

test(param: {
    print("***")
})

print("-------------------------------------")
//简化调用
test {
    print("***")
}


func test2(param:(Int) -> Void) {
    param(10)
}

test2(param: {(value:Int) -> Void in
    print(value)
})

test2(param: {(value) -> Void in
    print(value)
})

test2 { (value) in
    print(value)
}


func test3(param:(Int,Int) -> Int) {
    print(param(3,5))
}

test3 { (a, b) -> Int in
    return a + b
}
//$0 和 $1 分别取第一个和第二个参数
test3(param: {return $0+$1})

test3(param: {$0+$1})

闭包

闭包:可以看成加了语法糖的函数。闭包在语法上有这样的标准结构: { (parameters) -> return type in statements }就是{(参数列表) -> 返回值 in 闭包体}。并且闭包可以像函数一样被调用。

函数的设计思路是将有一定功能的代码块包装在一起,通过函数名实现复用。闭包和函数有着类似的作用,然而闭包的设计大多数情况下并不是为了代码的复用,而是传递功能代码块和处理回调结构。

func myFunc(param:Int) -> Int {
    return param * param
}
//将上面的函数用闭包实现
let myClosures = {(param:Int) -> Int in
    return param * param
}

与函数不同的是,闭包的返回值可以省略,在闭包中,如果有return返回,则闭包会自动将return的数据作为闭包的返回类型:

let myClosures = {(param: Int) -> Int in
    return param * param
}
//两种写法等价
let myClosures1 = {(param: Int) in
    return param * param
}
myClosures1(3)//9

在 函数 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:

  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
  • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

下面通过实现一个排序函数来深入理解闭包,开发者经常会遇到不同的排序需求,但是排序的对象并不是简单的数字类型,而是自定义的类。对于这种排序该如何实现呢?

首先明确需求:

  1. 应该实现一个排序函数,来对数组进行排序
  2. 数组中的元素可以是任意复杂类型

要实现对自定义类型的排序操作,需编写一个如下结构的函数,需要排序的数组和排序算法作为参数:

func mySort(array:Array<Any>, sortClosure:(Int, Int) -> Bool) -> Array<Any> {
    return array
}

排序闭包sortClosure有两个int类型参数,返回一个bool值,返回true则表示正序,返回false则表示倒序,需要交换两个元素的位置。继续完善mySort

func mySort(array: inout Array<Any>, sortClosure:(Int, Int) -> Bool) -> Array<Any> {

    guard !array.isEmpty else { return [] }
    for i in 0..<array.count {
        for j in 0..<array.count - i - 1 {
            if sortClosure(j, j + 1) {
                let tmp = array[j]
                array[j] = array[j + 1]
                array[j + 1] = tmp
            }
        }
    }
    return array
}

var array: Array<Any> = [1, 4, 3, 5, 7, 2, 6]

mySort(array: &array, sortClosure:{ (index:Int, nextIndex:Int) -> Bool in

    return (array[index] as! Int) > (array[nextIndex] as! Int)

})

print(array)

将闭包作为参数时,还可以进行逐步的写法优化:

/***
    sorted(by:)方法是Swift基本库的方法,用来对集合类型的数据进行排序,如数组。
    sorted(by:)的作用:对已知类型的数组进行排序。
    sorted(by:)的参数:(String, String) -> Bool。
    下面对字符串数组进行排序,以说明闭包的几种应用形式
    ***/
    
    /** 1. 使用函数 **/
    func backward(_ s1: String, _ s2: String) -> Bool {
        return s1 > s2
    }
    let a = names.sorted(by:backward)
    print("1. ", a)
    // Prints ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    
    /** 2. 使用闭包,闭包类型与参数类型相同 **/
    let b = names.sorted(by:{(s1: String, s2: String) -> Bool in return s1 > s2})
    print("2. ", b)
    // Prints ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    
    /** 3. 使用闭包,可从上下文推断其参数与返回数据的类型,所以可优化为 **/
    let c = names.sorted(by:{s1, s2 in return s1 > s2})
    print("3. ", c)
    // Prints ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    
    /** 4. 使用闭包,因闭包只含单条语句,则可省掉“return”,所以可优化为 **/
    let d = names.sorted(by:{s1, s2 in s1 > s2})
    print("4. ", d)
    // Prints ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    
    /** 5. 使用闭包,Swift自动为闭包提供$0,$1,$2之类,表示传入的参数,所以可以优化为 **/
    let e = names.sorted(by:{$0 > $1})
    print("5. ", e)
    // Prints ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    
    /** 6. 使用闭包,用这种更短的方式,是基于大于号在Swift中的定义,        **/
    /**    它的定义与sorted(by:)的参数类型相同,即比较两个字符串并返回布尔值  **/
    let f = names.sorted(by: > )
    print("6. ", f)
    // Prints ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

尾随闭包

如果需要将一个很长的闭包表达式作为函数的最后一个参数,如此嵌套冗长的写法在视觉上十分不直观,对于这种情况,可以将最后一个参数(闭包),脱离函数的参数列表,最佳在函数的尾部,增强代码的可读性。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}

上面sorted(by:)最后的两种优化方式就是尾随闭包的应用。当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

let strings = numbers.map {
    (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// strings 常量被推断为字符串类型数组,即 [String]
// 其值为 ["OneSix", "FiveEight", "FiveOneZero"]

值获取

上面说了,嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包。意思是闭包可以捕获定义在上下文中的常量和变量,即使这些常量和变量的作用域已经不存在,依然可以在闭包内引用和修改这些值。嵌套函数,就是值获取最好的实践。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

incrementer()函数中使用了外部定义的runningTotalamount值获取保证了当我们调用完makeIncrementer函数后,runningTotalamount两个变量不会立刻释放。

let incrementByTen = makeIncrementer(forIncrement: 10) //() -> Int
incrementByTen() // 返回的值为10
incrementByTen() // 返回的值为20
incrementByTen() // 返回的值为30

这里我觉得可以理解为一种强引用,incrementer()闭包对runningTotalamount的循环强引用。

再创建一个常量,继续调用makeIncrementer(),完全不受incrementByTen的影响,说明闭包是引用类型,所以无论讲闭包赋值给一个常量或者变量,实际都只是赋值了闭包的引用而不是闭包本身。

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()  // 返回的值为7

逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

把闭包保存在外部定义的变量中,是一种能使闭包逃逸出函数的方式,这种情况下,如果参数不标记为@escaping,就会得到一个编译错误。

还有一个注意点就是,逃逸闭包内,必须显示的引用self.

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”

自动闭包

自动闭包,可以理解为是一种便利的语法,用来包装函数的参数,什么样的参数呢?就是一个没有入参的闭包,被包装成一个表达式,可以省略闭包的花括号。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)

func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出“Now serving Alex!”

上面示例代码中的serve(customer:)函数的参数,就是一个没有入参的闭包,所以我们再调用时,就要传入一个显式的闭包,如果把customer参数标记为@autoclosure,就可以接收一个自动闭包。我们就可以传入一个String类型的参数,它会自动将参数转换为一个闭包来使用。

func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))

自动闭包也是可以“逃逸”的,只要同时标记@autoclosure@escaping属性。

// customersInLine i= ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// 打印“Collected 2 closures.”
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 打印“Now serving Barry!”
// 打印“Now serving Daniella!”

customerProvider可以被添加到外部定义的customerProviders数组中,说明customerProvider闭包可以在collectCustomerProviders函数执行完之后被调用,就是“逃逸”出了函数作用域。