zoukankan      html  css  js  c++  java
  • swift中的闭包总结

    闭包是功能性自包含模块,可以在代码中被传递和使用。 Swift 中的闭包与 Objective-C中的 blocks 以及其他一些编程语言中的 lambdas 比较相似。

    闭包的基本语法

    闭包表达式语法

    1
    2
    3
    { (paramenters) -> returnType in
    statements
    }

    例如 : 我们将一堆字符串进行排序,闭包表达式版本的代码为:

    1
    2
    3
    4
    5
    let strs = ["wangju","libai","dumu","liuyong"]

    var sortStrs = strs.sort ({ (s1 : String, s2 :String) -> Bool in
    return s1 < s2
    })

    闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。

    闭包以参数的形式传递给了sort,从而确定排序的方式 ,形如: sort( {闭包} )

    由于这个闭包比较短,我们可以将它改写成下面的方式 :

    1
    strs.sort ({ (s1 : String, s2 :String) -> Bool in return s1 < s2 })

    闭包根据上下文推断类型

    闭包函数是作为sort(_:)方法的参数传入的,swift可以根据传入的参数自动推断返回值类型

    • s1 ,s2 可以根据字符串数组的调用推断为String类型
    • 由此也可以推断返回值必须为Bool类型

    这意味着(String, String)和Bool类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略

    1
    strs.sort( { s1,s2 in return s1 < s2} )

    单表达式闭包隐式返回

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

    1
    strs.sort( { s1,s2 in s1 < s2} )

    参数名缩写

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

    如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

    1
    strs.sort( { $0 < $1 } )

    其中 $0 表示第一个参数 , $1 表示第二个参数,如果后面还有其他的参数,依次类推

    运算符函数

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

    1
    sortStrs = strs.sort(<)

    尾随闭包

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

    例如定义一个接收一个闭包作为参数的函数 :

    1
    2
    3
    func (closure: () -> Void) {

    }
    • 不用尾随闭包的调用方式
    1
    2
    3
    4
    // 以下是不使用尾随闭包进行函数调用
    someFunctionThatTakesAClosure({
    // 闭包主体部分
    })
    • 使用尾随闭包的调用方式
    1
    2
    3
    4
    // 以下是使用尾随闭包进行函数调用
    someFunctionThatTakesAClosure() {
    // 闭包主体部分
    }

    所以上面的sort函数可以根据下面的方法调用

    1
    2
    strs.sort(){ s1,s2 in return s1 < s2}
    sortStrs = strs.sort(){ $0 > $1 }

    如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉:

    1
    2
    strs.sort{ s1,s2 in return s1 < s2}
    sortStrs = strs.sort{ $0 > $1 }

    捕获值

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

    Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 该函数接收一个Int类型的参数,并返回一个参数为空,返回值为Int类型的闭包
    func makeIncrementor(forIncrement amount: Int) -> () -> Int {

    // 定义runningTotal的值为0
    var runningTotal = 0
    // 在闭包中做相加操作,每次相加的值都为接收的参数amount
    func incrementor() -> Int {
    // 在闭包中捕获runningTotal
    runningTotal += amount
    // 返回相加后的结果
    return runningTotal
    }
    // 返回该闭包
    return incrementor
    }

    使用makeIncrementor

    1
    2
    3
    4
    5
    6
    7
    8
    let incrementByTen = makeIncrementor(forIncrement: 10)

    incrementByTen()
    // 返回的值为10
    incrementByTen()
    // 返回的值为20
    incrementByTen()
    // 返回的值为30

    在返回的闭包还没有释放之前,swift会将捕获的值保存一份对值得拷贝,并自动管理捕获变量的生命周期

    闭包是引用类型

    这也意味着如果您将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:

    非逃逸闭包

    当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注@noescape,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注@noescape能使编译器知道这个闭包的生命周期,使被传入的闭包不能被外界引用或者返回

    cousure1

    将闭包标注为@noescape使你能在闭包中隐式地引用self。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 闭包数组,用来保存someFunctionWithEscapingClosure传入的闭包用来以后用到的时候调用
    var completionHandlers: [() -> Void] = []

    func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
    // closure 闭包只能在该函数体中调用
    closure()
    }

    func someFunctionWithEscapingClosure( completionHandler: () -> Void) {
    // 被外部引用
    completionHandlers.append(completionHandler)
    }

    class SomeClass {
    var x = 10
    func doSomething() {
    // 可逃逸的闭包不能隐式的使用self
    someFunctionWithEscapingClosure { self.x = 100 }
    // 不可逃逸闭包隐式使用self
    someFunctionWithNoescapeClosure { x = 200 }
    }
    }

    测试结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let instance = SomeClass()

    // doSomething 分别调用函数传入了两个闭包
    // someFunctionWithNoescapeClosure 在函数体中直接调用,所以修改x的值为200
    // someFunctionWithEscapingClosure 可逃逸闭包将闭包保存到completionHandlers并没有实际调用
    instance.doSomething()
    print(instance.x)
    // prints "200"

    // 调用 completionHandlers 中保存的闭包 { self.x = 100 }
    completionHandlers.first?()
    print(instance.x)
    // prints "100”

    自动闭包

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    print(customersInLine.count)
    // prints "5"

    // 保存一个闭包,但是并没有执行该闭包,所以0位置的元素并没有被移除
    let customerProvider = { customersInLine.removeAtIndex(0) }
    print(customersInLine.count)
    // prints "5"

    // 执行保存的闭包,该语句执行完 0位置的元素被移除
    print("Now serving (customerProvider())!")
    print(customersInLine.count)
    // prints "4

    尽管在闭包的代码中,customersInLine的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除

    @autoclosure 可以将接收的表达式自动的包装为一个闭包

    1
    2
    3
    4
    5
    6
    7
    // serveCustomer 中用 @autoclosure 将接收的参数包装为一个() -> String类型的闭包
    func serveCustomer(@autoclosure customerProvider: () -> String) {
    大专栏  swift中的闭包总结_in">print("Now serving (customerProvider())!")
    }

    // 执行该函数,并将传递的customersInLine.removeAtIndex(0)包装为() -> String传递
    serveCustomer(customersInLine.removeAtIndex(0))

    @autoclosure特性暗含了@noescape特性,默认创建的闭包是不可逃逸的,如果想要使闭包逃逸,可以使用@autoclosure(escaping)特性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    customersInLine = ["Barry", "Daniella"]
    var customerProviders: [() -> String] = []
    // 函数自动闭包,并且闭包是可以逃逸的
    func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
    // 闭包顺利的被外界引用,逃逸
    customerProviders.append(customerProvider)
    }
    // 执行函数,将customersInLine.removeAtIndex(0)自动闭包,被customerProviders引用,此时闭包并没有被执行
    collectCustomerProviders(customersInLine.removeAtIndex(0))
    collectCustomerProviders(customersInLine.removeAtIndex(0))

    // customerProviders 中引用了两个闭包
    print("Collected (customerProviders.count) closures.")

    // 执行 customerProviders 的所有闭包
    for customerProvider in customerProviders {
    // 移除 元素,并将该移除的元素打印
    //public mutating func removeAtIndex(index: Int) -> Element
    print("Now serving (customerProvider())!")
    }
    // prints "Now serving Barry!"
    // prints "Now serving Daniella!”

    闭包的额外用法

    函数的柯里化

    Swift 里可以将方法进行柯里化 (Currying),也就是把接受多个参数的方法变换成接受第一个参数的方法,并且返回接受余下的参数并且返回结果的新方法

    如两个数字求和

    1
    2
    3
    func addTwoNumbers(a: Int, num: Int) -> Int {
    return a + num
    }
    • 可以将该方法变为接收两个参数的方式(这种方式编译器实现了自动闭包)
    1
    2
    3
    4
    5
    6
    func addTwoNumbers(a: Int) (_ num: Int) -> Int {
    return a + num
    }
    // addtoSix 为(num: Int) -> Int的引用
    let addtoSix = addTwoNumbers(6)
    let result = addtoSix(7) // result = 13

    这种方法由于不好理解,会发出警告,估计马上会被官方弃用

    • 第二种方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 接收一个判断的数值,并返回一个闭包
    func greaterThan(comparor: Int) -> (input : Int) -> Bool{
    return { $0 > comparor }
    }

    let greaterThan10 = greaterThan(10)

    greaterThan10(input: 13) // 结果是 true
    greaterThan10(input: 9) // 结果是 false

    返回闭包类型的表示方式

    同样是上面的代码

    1
    2
    3
    func greaterThan(comparor: Int) -> (input : Int) -> Bool{
    return { $0 > comparor }
    }

    上面的闭包可以隐藏形参名,变成下面这种方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 接收一个判断的数值,并返回一个闭包
    func greaterThan(comparor: Int) -> (Int) -> Bool{
    return { $0 > comparor }
    }

    let greaterThan10 = greaterThan(10)

    greaterThan10(13) // 结果是 true
    greaterThan10(9) // 结果是 false

    我们也可以将传参的()一起忽略,变成最终这样,所以下面的返回值表示接收了一个Int的参数,并返回了一个Bool的闭包

    1
    2
    3
    4
    // 接收一个判断的数值,并返回一个闭包
    func greaterThan(comparor: Int) -> Int -> Bool{
    return { $0 > comparor }
    }

    函数返回的箭头问题

    函数中的箭头 -> 向右结合。这也就是说,你可以将 A -> B -> C 理解为 A -> (B -> C)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    func add(x : Int) -> Int -> Int
    {
    return {$0 + x}
    }

    // 所以上面的就理解为函数返回一个闭包,闭包接收一个Int的参数,并返回Int的结果
    func add1(x : Int) -> (Int -> Int)
    {
    return {$0 + x}
    }

    // 测试 打印3
    print(add(1)(2))

    let addNum = add1(4)
    // 输出10
    print(addNum(6))

    用自动闭包实现可选值??

    ?? 运算符。使用这个运算符时,需要额外提供一个默认值,当运算符被运用于 nil 时,这个默认值将被作为返回值。简单来说,它可以定义为下面这样
    (该运算符官方默认已经实现)

    1
    2
    3
    4
    5
    6
    7
    func ??<T>(optional: T?, defaultValue: T) -> T {
    if let x = optional {
    return x
    } else {
    return defaultValue
    }
    }

    ?? 运算符会检验它的可选参数是否为 nil。如果是,返回 defaultValue 参数;否则,返回可选值中实际的值。

    optional ?? defaultValue
    例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let dict = ["name":"wagju","age":"20"]

    var optionalHeight: Double? {
    get
    {
    if let height = dict["height"]
    {
    return Double(height)
    }
    return nil
    }

    }
    // 因为字典中没有height的键值,所以为nil,调用defaultValue
    let height = optionalHeight ?? 62

    print(height)

    在这个例子中,default的运算在传参的时候已经被执行,如果 optional 变量是非 nil 的话,我们真的不愿意对 defaultValue 进行求值 —— 因为这可能是一个开销非常大的计算,只有绝对必要时我们才会想运行这段代码。可以按如下方式解决这个问题:

    1
    2
    3
    4
    5
    6
    7
    func ??<T>(optional: T?, defaultValue: () -> T) -> T {
    if let x = optional {
    return x
    } else {
    return defaultValue()
    }
    }

    以上代码把defaultValue的运算延迟到用到defaultValue并且需要返回的时候

    但是这样写的话我们必须要传递一个闭包

    1
    myOptional ?? { myDefaultValue }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class ACell
    {

    var frame : CGRect
    {
    get
    {
    for _ in 0...11000 {
    print("模拟大量的运算")
    }
    return CGRect(x: 1,y: 2, 3,height: 4)
    }
    }

    }
    let otherFrame : CGRect? = CGRect(x: 1,y: 1, 1,height: 1)
    let myFrame = otherFrame ?? { ACell().frame }

    所以,我们可以使用隐式闭包来定义??,从而实现有选择的执行传值问题

    1
    2
    3
    4
    5
    6
    7
    8
    infix operator ?? { associativity right precedence 110 }
    func ??<T>(optional: T?, @autoclosure defaultValue: () -> T) -> T {
    if let x = optional {
    return x
    } else {
    return defaultValue()
    }
    }
  • 相关阅读:
    POJ 3261 Milk Patterns (求可重叠的k次最长重复子串)
    UVaLive 5031 Graph and Queries (Treap)
    Uva 11996 Jewel Magic (Splay)
    HYSBZ
    POJ 3580 SuperMemo (Splay 区间更新、翻转、循环右移,插入,删除,查询)
    HDU 1890 Robotic Sort (Splay 区间翻转)
    【转】ACM中java的使用
    HDU 4267 A Simple Problem with Integers (树状数组)
    POJ 1195 Mobile phones (二维树状数组)
    HDU 4417 Super Mario (树状数组/线段树)
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12288885.html
Copyright © 2011-2022 走看看