• 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()
    }
    }
  • 相关阅读:
    POJ2777
    链表
    模板
    poj 3468(线段树)
    用react编写一个hello world
    用express快速写一个hello world
    naturalWidth与naturalHeight
    div里面的图片垂直居中
    js将网址转为二维码并下载图片
    记一个视频播放器插件 video.js
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12288885.html
Copyright © 2020-2023  润新知