美文网首页
Swift5.5学习笔记六:闭包(Closures)

Swift5.5学习笔记六:闭包(Closures)

作者: ma_yongsong | 来源:发表于2021-11-25 12:28 被阅读0次

    //闭包(Closures)
    //闭包是独立的功能块,可以在代码中传递和使用。
    //Swift中的闭包类似于 C 和 Objective-C 中的Block以及其他编程语言中的 lambda

    //一、闭包表达式
    //闭包表达式是一种以简短、重点突出的语法编写内联闭包的方法。
    //闭包表达式提供了多种语法优化为以缩短的形式编写闭包,而不会失去清晰度或意图。

    //1.排序方法
    //Swift的标准库提供了一个名为sorted(by:)的方法,该方法根据您提供闭包的输出的排序对已知类型的数组值进行排序。
    //一旦完成排序过程,该sorted(by:)方法将返回一个与旧数组具有相同类型和大小的新数组,其元素按正确的排序顺序排列。原始数组不会被该sorted(by:)方法修改。
    //下面的闭包表达式示例使用该sorted(by:)方法按逆字母顺序对一组String值进行排序。这是要排序的初始数组:

    let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    

    //该sorted(by:)方法接受一个闭包,该闭包采用与数组内容相同类型的两个参数,并根据第一个值应该出现在第二个值之前还是之后来返回一个Bool值说明值如何进行排序
    //如果第一个值应该出现在第二个值之前,则排序闭包需要返回true,否则返回false。

    //以下这个例子是对一个String值数组进行排序,排序闭包需要是一个类型为(String, String) -> Bool的函数

    func backward(_ s1: String, _ s2: String) -> Bool {
        return s1 > s2
    }
    var reversedNames = names.sorted(by: backward)
    // reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    //如果第一个字符串(s1)大于第二个字符串(s2),函数backward(::)将返回true,表示排序数组中s1应该出现在s2之前
    //对于字符串中的字符,“大于”表示“在字母表中出现的晚”。这意味着字母"B"“大于”字母"A",并且字符串"Tom"大于字符串"Tim"。
    //这给出了反向字母排序,"Barry"放置在"Alex"之前,依此类推。

    //2.闭包表达式语法
    //闭包表达式语法具有以下一般形式:

    { (parameters) -> return type in
        statements
    }
    

    //该参数在封闭表达式语法可以是输入输出参数,但是他们不能有一个默认值。
    //如果您命名的是可变参数,则可以使用可变参数。元组也可以用作参数类型和返回类型。

    //下面的示例显示了上述backward(::)函数的闭包表达式版本:

    reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
        return s1 > s2
    })
    print(reversedNames)
    //["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    //请注意,此内联闭包的参数和返回类型的声明与backward(::)函数的声明相同。
    //在这两种情况下,它都写为(s1: String, s2: String) -> Bool
    //但是,对于内联闭包表达式,参数和返回类型写在花括号内,而不是在花括号外。
    //闭包主体的开始由in关键字开始。这个关键字表示闭包的参数和返回类型的定义已经完成,闭包的主体即将开始
    //因为闭包的主体很短,它甚至可以写成一行:

    reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
    print(reversedNames)
    //["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    //3.从上下文推断类型
    //因为排序闭包作为参数传递给方法,Swift可以推断其参数的类型和它返回的值的类型。
    //该sorted(by:)方法是在字符串数组上调用的,因此其参数必须是类型为(String, String) -> Bool的函数。
    //这意味着(String, String)和Bool类型不需要写成闭包表达式定义的一部分。因为可以推断所有类型,所以也可以省略返回箭头(->)和参数名称周围的括号

    reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
    print(reversedNames)
    //["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    //4.单表达式闭包的隐式返回
    //单表达式闭包可以省略关键字return,通过从声明中隐式返回其单个表达式的结果
    //如上例的版本可以修改为:

    reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
    print(reversedNames)
    //["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    //5.简写参数名称
    //Swift自动为内联闭包提供参数名称简写,可通过参数0、1、$2推断简写参数名称的值
    //如果您在闭包表达式中使用这些简写参数名称,则可以从其定义中省略闭包的参数列表。
    //简写参数名称的类型是从预期的函数类型推断出来的,您使用的编号最高的简写参数名称决定了闭包采用的参数数量。
    //关键字in也可以被省略,因为闭包表达式完全是有闭包创建的

    reversedNames = names.sorted(by: { $0 > $1 } )
    print(reversedNames)
    //["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    //在这里,0和1引用闭包的第一个和第二个字符串参数。
    //因为1是编号最高的简写参数,所以闭包被理解为带有两个参数。 //因为这里的函数sorted(by:)需要一个闭包,它的参数都是字符串,简写参数0和$1都是String类型

    //6.运算符方法
    //实际上有一种更短的方法来编写上面的闭包表达式。
    //Swift的String类型将大于运算符(>)的字符串特定实现定义为一个方法,该方法具有两个String类型参数,并返回一个Bool类型值。
    //这与sorted(by:)方法所需的方法类型完全匹配。因此,您可以简单地传入大于运算符(>),Swift将推断您要使用其字符串特定的实现:

    reversedNames = names.sorted(by: >)
    print(reversedNames)
    //["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    //二、尾随闭包
    //如果您需要将闭包表达式作为函数的最终参数传递给函数,并且闭包表达式很长,那么将其写为尾随闭包会很有用。
    //你在函数调用的括号之后写了一个尾随闭包,即使尾随闭包仍然是函数的一个参数。
    //当您使用尾随闭包语法时,您不会将第一个闭包的参数标签作为函数调用的一部分。
    //一个函数调用可以包含多个尾随闭包;但是,下面的前几个示例使用单个尾随闭包。

    func someFunctionThatTakesAClosure(closure: () -> Void) {
        // function body goes here
        print("someFunctionThatTakesAClosure")
    }
    
    // Here's how you call this function without using a trailing closure:
    someFunctionThatTakesAClosure(closure: {
        // closure's body goes here
    })
    
    // Here's how you call this function with a trailing closure instead:
    
    someFunctionThatTakesAClosure() {
        // trailing closure's body goes here
    }
    

    //上面闭包表达式语法部分的字符串排序闭包可以写在sorted(by:)方法的括号之外作为尾随闭包:

    reversedNames = names.sorted() { $0 > $1 }
    print(reversedNames)
    

    //如果闭包表达式作为函数或方法的唯一参数提供,并且您将该表达式作为尾随闭包提供
    //当在调用函数时,则无需在函数或方法名称后写一对括号():

    reversedNames = names.sorted { $0 > $1 }
    

    //当闭包足够长以至于无法将其内联写入一行时,尾随闭包最有用。
    //例如,Swift的Array类型有一个map(:)方法,它接受一个闭包表达式作为它的单个参数,
    //为数组中的每个项目调用一次闭包,并为该项目返回一个替代的映射值(可能是其他类型的)
    //您可以通过map(
    :)方法,在闭包中编写代码来指定映射的性质和返回值的类型

    //以下是使用map(_:)方法将带有尾随闭包的Int值数组转换为String值数组。
    //该数组用于创建新数组:[16, 58, 510] ["OneSix", "FiveEight", "FiveOneZero"]

    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]
    

    //您现在可以使用numbers数组来创建String值数组,方法是将闭包表达式map(_:)作为尾随闭包传递给数组的方法:

    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
    }
    
    print(strings)
    // strings is inferred to be of type [String]
    // its value is ["OneSix", "FiveEight", "FiveOneZero"]
    

    //该map(_:)方法为数组中的每个项目调用一次闭包表达式。
    //您不需要指定闭包输入参数number的类型,因为可以从要映射的数组中的值推断出类型。

    //三、捕捉值
    //在Swift 中,可以捕获值的最简单的闭包形式是嵌套函数,它写在另一个函数的主体中。
    //嵌套函数可以捕获其外部函数的任何参数,也可以捕获外部函数中定义的任何常量和变量。

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

    //下面是一个调用makeIncrementer的例子:
    let incrementByTen = makeIncrementer(forIncrement: 10)
    //这个例子设置了一个被调用的常量incrementByTen来引用一个增量函数,每次调用runningTotal时它都会添加10到它的变量中。
    //多次调用该函数显示了这种行为:

    incrementByTen()
    // returns a value of 10
    incrementByTen()
    // returns a value of 20
    incrementByTen()
    // returns a value of 30
    

    //如果您创建第二个增量器,它将拥有自己存储的对新的单独runningTotal变量的引用:

    let incrementBySeven = makeIncrementer(forIncrement: 7)
    incrementBySeven()
    // returns a value of 7
    

    //incrementByTen再次调用原来的incrementer()会继续增加它自己的runningTotal变量,并且不会影响被incrementBySeven捕获的变量:

    incrementByTen()
    // returns a value of 40
    

    //备注:如果您将闭包分配给类实例的属性,并且该闭包通过引用该实例或其成员来捕获该实例,那么您将在该闭包和该实例之间创建一个强引用循环。
    //Swift使用捕获列表来打破这些强引用循环

    //四、闭包是引用类型
    //在上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量所指的闭包仍然能够捕获变量runningTotal增加。这是因为函数和闭包是引用类型。
    //每当您将函数或闭包分配给常量或变量时,您实际上是在将该常量或变量设置为对该函数或闭包的引用。
    //这也意味着,如果您将一个闭包分配给两个不同的常量或变量,那么这两个常量或变量都指向同一个闭包。

    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()
    // returns a value of 50
    incrementByTen()
    // returns a value of 60
    

    //上面的例子表明调用alsoIncrementByTen与调用incrementByTen相同。
    //因为它们都引用同一个闭包,所以它们都递增并返回相同的运行总数。

    //五、转义闭包
    //当闭包作为参数传递给函数时,闭包被称 为对函数进行转义,在函数返回后被调用。
    //当您声明一个函数将闭包作为其的参数之一时,您可以在参数类型之前写入”@escaping“以说明允许闭包转义。

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

    //该someFunctionWithEscapingClosure(_:)函数将一个闭包作为其参数并将其添加到在函数外部声明的数组中。
    //如果你没有用@escaping标记这个函数的参数,会发生编译时错误。
    //如果self引用了一个引用self自己的的转义闭包,需要特别考虑一下self。在转义闭包中捕获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)
    // Prints "200"
    
    completionHandlers.first?()
    print(instance.x)
    // Prints "100"
    

    //这是通过self包含在闭包的捕获列表中来捕获的self的doSomething()版本,这里self隐式引用

    class SomeOtherClass {
        var x = 10
        func doSomething() {
            someFunctionWithEscapingClosure { [self] in x = 100 }
            someFunctionWithNonescapingClosure { x = 200 }
        }
    }
    

    //如果self是结构或枚举,则始终可以隐式引用self
    //但是,当self是结构或枚举的情况,可变引用转义闭包无法捕获self

    struct SomeStruct {
        var x = 10
        mutating func doSomething() {
            someFunctionWithNonescapingClosure { x = 200 }  // Ok
    //        someFunctionWithEscapingClosure { x = 100 }     // Error
        }
    }
    

    //对上面示例中someFunctionWithEscapingClosure的函数的调用会报错误,因为self在一个可变方法中,所以是可变的。这违反了在结构中,转义闭包不能捕获可变引用self的规则

    //六、自动闭包
    //一个自动闭包是自动创建一个表达式作为参数传递给函数的闭包。当它被调用时,不用带任何参数,它返回被包含在里面的的表达式的值。
    //通过编写一个普通表达式而不是显示的闭包,这种语法方便让你忽略函数参数前后的大括号
    //自动闭包调用函数非常常见,但是通常不实现这种函数
    //比如:assert(condition:message:file:line:) 方法给它的条件和消息参数使用一个自动闭包
    //它的条件参数仅在debug构建的时候被使用,并且仅在它的条件参数为false的时候被使用
    //自动闭包让你延迟执行代码,因为内部代码直到你调用自动闭包的时候才会执行
    //延迟执行对于有副作用或者计算量大的代码非常有用,因为自动闭包让你可以控制什么时候执行代码
    //下面的代码示例了自动闭包如何执行

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

    //上面的代码中,即使customersInLine数组的第一个元素在自动闭包代码中被删除,数组元素直到自动闭包真实执行才被删除
    //假如闭包一直不调用,闭包里面的表达式将永远不会被执行,这意味着数组元素将不会被删除
    //这里,customerProvider的类型不是字符串,而是一个午餐返回值为String的() -> String函数

    //当你传递一个闭包给一个函数作为参数,延迟执行将同样有效

    // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
    func serve(customer customerProvider: () -> String) {
        print("Now serving \(customerProvider())!")
    }
    serve(customer: { customersInLine.remove(at: 0) } )
    // Prints "Now serving Alex!"
    

    //上面的代码中,serve(customer:)函数使用显示闭包返回一个元素的值。
    //在下面的serve(customer:)函数示例中,将不使用显示闭包,而是通过在函数参数类型上加一个@autoclosure属性,使用自动闭包实现同样的效果
    //现在你可以调用向函数传递String参数而不是闭包一样调用函数
    //因为customerProvider的参数类型标记了@autoclosure属性,参数会自动传递给闭包

    // customersInLine is ["Ewa", "Barry", "Daniella"]
    func serve(customer customerProvider: @autoclosure () -> String) {
        print("Now serving \(customerProvider())!")
    }
    serve(customer: customersInLine.remove(at: 0))
    // Prints "Now serving Ewa!"
    
    print(customersInLine)
    //Prints "["Barry", "Daniella"]"
    

    //备注:过度使用自动闭包将是你的代码难以理解,上下文和函数名称应该清楚地表明正在延迟执行

    //如果你希望自动闭包允许被转义,你可以同时使用@autoclosure和@escaping属性

    // customersInLine is ["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.")
    // Prints "Collected 2 closures."
    
    for customerProvider in customerProviders {
        print("Now serving \(customerProvider())!")
    }
    // Prints "Now serving Barry!"
    // Prints "Now serving Daniella!"
    
    
    print(customersInLine)
    //Prints "[]"
    

    //在上面的代码中,collectCustomerProviders(_:)添加闭包到customerProviders数组,而不是调用闭包作为参数传递给customerProvider
    //数组被定义在函数作用域的外面,这意味着数组中的闭包可以在函数返回后执行
    //因此,customerProvider必须允许参数脱离函数的作用域

    相关文章

      网友评论

          本文标题:Swift5.5学习笔记六:闭包(Closures)

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