美文网首页
Swift3.1_函数与闭包

Swift3.1_函数与闭包

作者: Carson_Zhu | 来源:发表于2018-02-21 12:51 被阅读8次

    函数

    当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为参数,也可以定义某种类型的值作为函数执行结束时的输出,称为返回类型。

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

    函数参数与返回值

    函数的定义以func作为前缀,后接方法名,后跟一对括号定义参数,参数用,隔开;指定函数返回类型时,用返回箭头->后跟返回类型的名称的方式来表示。

    无参数函数
    func sayHelloWorld() -> String {
        return "hello, world"
    }
    print(sayHelloWorld())
    // 打印 "hello, world"
    
    多参数函数
    func add(num1: Int, num2: Int) -> String {
        return "sum is \(num1+num2)"
    }
    print(add(num1: 3, num2: 5))
    // 打印 "sum is 8"
    
    无返回值函数
    func say(message: String) {
        print("say \(message)")
    }
    say(message: "hello")
    // 打印 say hello
    

    严格上来说,虽然没有返回值被定义,say(message:)函数依然返回了值。没有定义返回类型的函数会返回一个特殊的Void值。它其实是一个空的元组tuple,没有任何元素,可以写成()

    多重返回值函数

    你可以用元组tuple类型让多个值作为一个复合值从函数中返回。

    func minmax(array: [Int]) -> (min: Int, max: Int) {
        let min = array.min()!
        let max = array.max()!
        return (min, max)
    }
    let (min, max) = minmax(array: [2, 4, 9, 1])
    print("min: \(min), max: \(max)")
    // 打印 min: 1, max: 9
    
    可选元组返回类型

    如果函数返回的元组类型有可能整个元组都没有值,你可以使用可选的optional元组返回类型反映整个元组可以是nil的事实。

    可选元组类型如(Int, Int)?与元组包含可选类型如(Int?, Int?)是不同的。可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。

    func minmax(array: [Int]) -> (min: Int, max: Int)? {
        if array.count == 0 {
            return nil
        }
        let min = array.min()!
        let max = array.max()!
        return (min, max)
    }
    // 通过可选绑定取值
    if let (min, max) = minmax(array: [2, 4, 9, 1]) {
        print("min: \(min), max: \(max)")
    }
    

    函数参数标签和参数名称

    每个函数参数都有一个参数标签argument label以及一个参数名称parameter name。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。

    func someFunction(firstParameterName: Int, secondParameterName: Int) {
        // firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
    }
    someFunction(firstParameterName: 1, secondParameterName: 2)
    

    所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的函数标签能够使你的代码更具可读性。

    指定参数标签

    你可以在参数名称前指定它的参数标签,中间以空格分隔:

    func greet(person: String, from hometown: String) -> String {
        return "Hello \(person)! Glad you could visit from \(hometown)."
    }
    print(greet(person: "Bill", from: "Cupertino"))
    // 打印 "Hello Bill! Glad you could visit from Cupertino."
    
    忽略参数标签

    如果你不希望为某个参数添加一个标签,可以使用一个下划线_来代替一个明确的参数标签。

    func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
         // firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
    }
    someFunction(1, secondParameterName: 2)
    
    默认参数值

    你可以在函数体中通过给参数赋值来为任意一个参数定义默认值Deafult Value。当默认值被定义后,调用这个函数时可以忽略这个参数。

    func double(num: Int, multiple: Int = 2) -> Int {
        return num * multiple
    }
    print(double(num: 5))
    // 打印 10
    
    可变参数

    一个可变参数variadic parameter可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入...的方式来定义可变参数。

    func sum(_ numbers: Int...) -> Int {
        var sum = 0
        for num in numbers {
            sum = sum + num
        }
        return sum
    }
    print(sum(2, 3, 5))
    

    注意:一个函数最多只能拥有一个可变参数。

    输入输出参数

    函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数In-Out Parameters

    func swapTwoInts(_ a: inout Int, _ b: inout Int) {
        let temp = a;
        a = b
        b = temp
    }
    var a = 3
    var b = 5
    swapTwoInts(&a, &b)
    print("a: \(a), b: \(b)")
    // 打印 a: 5, b: 3
    

    函数类型

    每个函数都有种特定的函数类型,函数的类型由函数的参数类型和返回类型组成。

    func addTwoInts(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
    

    函数的类型是(Int, Int) -> Int

    func printHelloWorld() {
        print("hello, world")
    }
    

    函数的类型是() -> Void

    使用函数类型
    var mathFunction: (Int, Int) -> Int = addTwoInts
    print("Result: \(mathFunction(2, 3))")
    // Prints "Result: 5"
    

    addTwoIntsmathFunction有同样的类型,并让这个新变量指向addTwoInts函数”,就可以用mathFunction来调用被赋值的函数了。

    函数类型作为参数类型
    func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
        print("Result: \(mathFunction(a, b))")
    }
    printMathResult(addTwoInts, 3, 5)
    // 打印 "Result: 8"
    
    函数类型作为返回类型
    func stepForward(_ input: Int) -> Int {
        return input + 1
    }
    func stepBackward(_ input: Int) -> Int {
        return input - 1
    }
    
    func chooseStepFunction(backward: Bool) -> (Int) -> Int {
        return backward ? stepBackward : stepForward
    }
    

    嵌套函数

    到目前为止本章中你所见到的所有函数都叫全局函数global functions,它们定义在全局域中。你也可以把函数定义在别的函数体中,称作嵌套函数nested functions

    func getTwice(_ num: Int) -> () -> Int {
        let multiple = 2
        func twice () -> Int {
            return num * multiple
        }
        return twice
    }
    

    闭包

    Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:

    • 利用上下文推断参数和返回值类型
    • 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
    • 参数名称缩写
    • 尾随闭包语法

    闭包表达式

    闭包表达式语法有如下的一般形式:

    { (parameters) -> returnType in
        statements
    }
    

    sorted(by:)方法为例,

    let numbers = [3, 9, 5, 1, 7]
    

    sorted(by:)方法接受一个闭包,排序闭包函数类型需为(Int, Int) -> Bool

    func backward(_ num1: Int, _ num2: Int) -> Bool {
        return num1 > num2
    }
    

    然而,以这种方式来编写一个实际上很简单的表达式num1 > num2,确实太过繁琐了。对于这个例子来说,利用闭包表达式语法可以更好地构造一个内联排序闭包。

    let backwardNums = numbers.sorted(by: {(num1, num2) -> Bool in
        num1 > num2
    })
    
    根据上下文推断类型

    Swift可以推断其参数和返回值的类型,sorted(by:)方法被一个Int数组调用,因此其参数必须是(Int, Int) -> Bool类型的函数。这意味着参数和返回值类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断:

    let backwardNums = numbers.sorted(by: {num1, num2 in return num1 > num2})
    
    单表达式闭包隐式返回

    单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

    let backwardNums = numbers.sorted(by: {num1, num2 in num1 > num2})
    
    参数名称缩写

    Swift自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0$1$2来顺序调用闭包的参数,以此类推。

    let backwardNums = numbers.sorted(by: {$0 > $1})
    
    运算符方法

    实际上还有一种更简短的方式来编写上面例子中的闭包表达式。SwiftInt类型定义了关于大于号>的实现,其作为一个函数接受两个Int类型的参数并返回Bool类型的值。而这正好与sorted(by:)方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift可以自动推断出你想使用大于号的字符串函数实现:

    let backwardNums = numbers.sorted(by: >)
    

    尾随闭包

    如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:

    func myFunc(completion: () -> Void) {
        completion()
    }
    

    不使用尾随闭包进行函数调用:

    myFunc(completion: {
        print("hello world")
    })
    

    使用尾随闭包进行调用:

    myFunc() {
        print("hello world")
    }
    

    如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把()省略掉:

    myFunc {
        print("hello world")
    }
    

    值捕获

    闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

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

    如果我们单独考虑嵌套函数incrementer(),会发现它有些不同寻常:

    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    

    incrementer()函数并没有任何参数,但是在函数体内访问了 runningTotalamount变量。这是因为它从外围函数捕获了runningTotalamount变量的引用。捕获引用保证了runningTotalamount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal依旧存在。

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

    Swift闭包可以在其被定义的上下文中捕获常量或变量,而Objective-C需要使用__block捕获上下文中常量或变量;所以闭包不同于block

    闭包是引用类型

    上面的例子中,`incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。

    无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。

    解决闭包引起的循环强引用

    Swift有如下要求:只要在闭包内使用self的成员,就要用self.someProperty或者self.someMethod()(而不只是somePropertysomeMethod())。这提醒你可能会一不小心就捕获了self,造成循环强引用。

    定义捕获列表

    捕获列表中的每一项都由一对元素组成,一个元素是weakunowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如delegate = self.delegate!)。这些项在方括号中用逗号分开。

    如果闭包有参数列表和返回类型,把捕获列表放在它们前面:

    lazy var someClosure: (Int, String) -> String = {
        [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
        // 这里是闭包的函数体
    }
    

    如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方:

    lazy var someClosure: Void -> String = {
        [unowned self, weak delegate = self.delegate!] in
        // 这里是闭包的函数体
    }
    

    注意:

    在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。

    如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。

    逃逸闭包

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

    一种能使闭包逃逸出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:

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

    将一个闭包标记为@escaping意味着你必须在闭包中显式地引用self

    自动闭包

    自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。

    自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。下面的代码展示了闭包如何延时求值。

    var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    print(customersInLine.count)
    // 打印出 "5"
    
    let customerProvider = { customersInLine.remove(at: 0) }
    print(customersInLine.count)
    // 打印出 "5"
    
    print("Now serving \(customerProvider())!")
    // Prints "Now serving Chris!"
    print(customersInLine.count)
    // 打印出 "4"
    

    相关文章

      网友评论

          本文标题:Swift3.1_函数与闭包

          本文链接:https://www.haomeiwen.com/subject/ktnntftx.html