闭包方法的学习

swift中使用@noescape的正确姿势

文档闭包

闭包可以捕获和存储其在上下文中任意常量和变量的引用。被称为包裹常量和变量。Swift会为你管理在捕获过程中涉及到的所有内存操作。

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

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

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

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

闭包表达式

sorted方法
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

let names = ["C","A","E","D","B"]
//        let reversedNames = names.sorted(by: {$0 > $1})
   // 两者相同
let reversedNames = names.sorted(by: backward)
print("\(reversedNames)")
闭包表达式语法
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
   return s1 > s2
})

// 也可以写一行
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
根据上下文推断类型

因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略

let reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })
单表达式闭包隐式返回

单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果

let reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })
参数名称所写

Swift自动为内联闭包提供了参数名称所写功能,你可以直接通过$0$1,$2来顺序调用闭包的参数,依次类推,如果你在闭包表达式中使用参数名称所写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因此此时闭包表达式完全由闭包函数体构成:

let reversedNames = names.sorted(by: { $0 > $1 })
运算符方法
let reversedNames = names.sorted(by: >)
尾随闭包法

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

         func someFunctionThatTakesAClosure(closure: (_ str: String) -> String) {
            // 函数体部分
            print("可以判断是谁调取的:\(closure("给你一"))")
         }

         // 以下是不使用尾随闭包进行函数调用
        someFunctionThatTakesAClosure(closure: { str in
            // 闭包主体部分
            print("返回二 \(str)")
            return "返回二"
            
        })
        
        // 以下是使用尾随闭包进行函数调用
        someFunctionThatTakesAClosure() { str in
            
            // 闭包主体部分
            print(str)
            return "返回三"
        }
        
        // sorted(by:) 也可以使用这样写
        let reversedNames = names.sorted() { $0 > $1 }

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

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

当闭包非常的长以至于不能在一行中进行书写时,尾随闭包变的非常有用。举例来说,Swift的 Array类型有一个 map(_:)方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。

当提供给数组的闭包应用于每个数组元素后,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)

map(_:) 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数的 number 的类型,因为可以通过要映射的数据类型进行推断

值捕获

注意

如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift使用捕获列表来打破这种循环强引用。


   func makeIncrement(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 依旧存在。

调用此函数方法

   func makeIncrement(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        
        func incrementer() -> Int {
            
            runningTotal += amount
            return runningTotal
        }
        print("runningTotal ****** \(runningTotal)")
        return incrementer
    }
    
    let incrementByTen = makeIncrement(forIncrement: 10)
    
    print(incrementByTen())
    print(incrementByTen())
    print(incrementByTen())
    print(incrementByTen())
    
    /*
    10
    20
    30
    40
    */

该例子定义了一个叫做 incrementByTen 的常量,该常量指向一个每次调用会将 runningTotal 变量增加 10incrementer 函数。如果从新创建一个 incrementer, 它会有属于自己的引用,指向一个全新、独立的 `runningTotal变量:

闭包是引用类型

上面的例子中,incrementBySevenincrementByTen 都是常量,但是这些常量指向的闭包仍然可以增加其不活动额变量的值。这是因为函数和闭包都是引用类型。
无论你将函数或闭包赋值给一个常量还是变量,你实际上都是讲常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用 incrementByTen 而并非闭包内容本省。
这也意味着如果你讲闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值为50
逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。 一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。

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

someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 @escaping,就会得到一个编译错误。
将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 self

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

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}


class someClass {
    var x = 10
    func doSomthing() {
        someFunctionWithEscapingClosure {
            self.x = 100
            print("调取了1")
        }
        
        someFunctionWithNonescapingClosure {
            x = 200
            print("调取了2")
        }
    }
}

let instance = someClass()
        
instance.doSomthing()
print(instance.x)

// 将函数中的闭包方法在函数外调取,相当于block异地调
completionHandlers.first?()
print(instance.x)

// 结果
调取了2
200
调取了1
100
自动闭包

自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种遍历语法让你能够省略闭包的花括号,用一个普通的表达式来代替显示的闭包。
自动闭包让你能够延迟求值,因为知道你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它能控制代码的执行时机。

var customersInLine = ["C","A","E","B","D","F"]
print(customersInLine.count)

// 此处只是一个简单的函数赋值,并不会执行移除
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)

print("现在,移除第一个元素 \(customerProvider())")

print(customersInLine.count)

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

将闭包作为参数传递给函数时,你能获得同样的延时求值行为。


// 自定义的一个自己需要的符合要求的函数式
func test() -> String {
    return "我很好"
}

// 在此处固定闭包函数类型
func serve(customer customerProvider: () -> String) {
    print("此处调取方法 \(customerProvider())")
}

// autoclosure的使用对比
func serveOne(customer customerProvider: @autoclosure () -> String ) {
    print("调取闭包方法:\(customerProvider())")
}

var customersInLine = ["C","A","E","B","D","F"]
print(customersInLine.count)

// 此处只是一个简单的函数赋值,并不会执行移除
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)

print("现在,移除第一个元素 \(customerProvider())")
print(customersInLine.count)

// 直接在自身处理闭包
serve { () -> String in
    // 这种是自定义
    return "你好啊"
}

// 在外部调取闭包
serve(customer: test)

// 这种是将固定类型的放进去
serve(customer: { customersInLine.remove(at: 0) })
// 加了autoclosure,相当于去掉了外部括号,不太好看出是自动闭包
serveOne(customer: customersInLine.remove(at: 0))

/**结果**/
6
6
现在移除第一个元素 C
5
此处调取方法 你好啊
此处调取方法 我很好
此处调取方法 A
调取闭包方法E

通过将参数标记为 @autoclosure 来接收一个自动闭包。现在你可以将该函数当做接受 String 类型参数(而非闭包)的函数来调用。customerProvider 参数将自动转化为一个闭包,因为该参数被标记了 @autoclosure 特性。

// autoclosure
func serveOne(customer customerProvider: @autoclosure () -> String ) {
    print("调取闭包方法:\(customerProvider())")
}

serveOne(customer: customersInLine.remove(at: 0))

注意:

过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。

如果你想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure@escaping 属性。

var customerProviders: [() -> String] = []

func collectCustomerProviders(_ customerProveder: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProveder)
}

var customersInLine = ["C","A","E","B","D","F"]

collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

for customerProvider in customerProviders {
    
    print("自动闭包:\(customerProvider())")
}


© 2017. All rights reserved.

Powered by Hydejack v7.5.0