Swift知识点7 - 闭包

http://wiki.jikexueyuan.com/project/swift/
http://www.swift51.com/swift4.0/

尾随闭包:闭包作为一个最后一个参数传给函数

闭包

闭包是可以在你的代码中被传递和引用的功能性独立模块。
闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用。

闭包表达式语法

{ (parameters) -> (return type) in
    statements
  }
let names = ["Chris","Alex","Ewa","Barry","Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
// 函数作为闭包传入
names.sorted(by: backward)

// 省去函数定义,直接定义闭包
names.sorted(by: { ( s1: String, s2: String) -> Bool in
    return s1 > s2
})

// 函数体只有一行,可以写为一行
names.sorted(by: {( s1: String, s2: String) -> Bool in return s1 > s2 } )

// 类型推断,可以省略参数类型及返回值
// 当把闭包作为行内闭包表达式传递给函数,形式参数类型和返回类型都可以被推断出来。所以说,当闭包被用作函数的实际参数时你都不需要用完整格式来书写行内闭包。
names.sorted(by: { s1, s2 in return s1 > s2 } )

// 从单表达式闭包隐式返回,删掉 return 关键字来隐式返回它们单个表达式的结果
names.sorted(by: { s1, s2 in s1 > s2 } )

// 简写的实际参数名

// Swift 自动对行内闭包提供简写实际参数名,你也可以通过 $0 , $1 , $2 等名字来引用闭包的实际参数值。
// 如果你在闭包表达式中使用这些简写实际参数名,那么你可以在闭包的实际参数列表中忽略对其的定义,并且简写实际参数名的数字和类型将会从期望的函数类型中推断出来。 in  关键字也能被省略
// in  关键字也能被省略
names.sorted(by: { $0 > $1 } )

运算符函数

实际上还有一种更简短的方式来撰写上述闭包表达式。Swift 的 String 类型定义了关于大于号( >)的特定字符串实现,让其作为一个有两个 String 类型形式参数的函数并返回一个 Bool 类型的值。这正好与 sorted(by:) 方法的第二个形式参数需要的函数相匹配。因此,你能简单地传递一个大于号,并且 Swift 将推断你想使用大于号特殊字符串函数实现:

reversedNames = names.sorted(by: >)

尾随闭包

如果你需要将一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾随闭包将增强函数的可读性。尾随闭包是一个被书写在函数形式参数的括号外面(后面)的闭包表达式:

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

如果闭包表达式被用作函数唯一的实际参数并且你把闭包表达式用作尾随闭包,那么调用这个函数的时候你就不需要在函数的名字后面写一对圆括号 ( )。

func someFunctionThatTakesAClosure(closure:() -> Void){
    //function body goes here
}
someFunctionThatTakesAClosure {
    
}

func someFunctionThatTakesAClosure(closure:() -> Void, closure2:() -> Void){
    //function body goes here
}
someFunctionThatTakesAClosure(closure: {
    
}) {
    
}

捕获值

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

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

作为一种优化,如果一个值没有改变或者在闭包的外面,Swift 可能会使用这个值的拷贝而不是捕获。
Swift也处理了变量的内存管理操作,当变量不再需要时会被释放。

如果你分配了一个闭包给类实例的属性,并且闭包通过引用该实例或者它的成员来捕获实例,你将在闭包和实例间建立一个强引用环。
Swift将使用捕获列表来打破这种强引用环。

闭包是引用类型

无论你什么时候安赋值一个函数或者闭包给常量或者变量,你实际上都是将常量和变量设置为对函数和闭包的引用。这上面这个例子中,闭包选择 incrementByTen 指向一个常量,而不是闭包它自身的内容。
这也意味着你赋值一个闭包到两个不同的常量或变量中,这两个常量或变量都将指向相同的闭包。

逃逸闭包

当闭包作为一个实际参数传递给一个函数的时候,我们就说这个闭包逃逸了,因为它可以在函数返回之后被调用。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。

闭包可以逃逸的一种方法是被储存在定义于函数外的变量里。

闭包被添加到数组中,表示可以逃逸,需要添加@excaping 标明。

比如说,很多函数接收闭包实际参数来作为启动异步任务的回调。函数在启动任务后返回,但是闭包要直到任务完成——闭包需要逃逸,以便于稍后调用。

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

让闭包 @escaping 意味着你必须在闭包中显式地引用 self ,比如说,下面的代码中,传给 someFunctionWithEscapingClosure(:) 的闭包是一个逃逸闭包,也就是说它需要显式地引用 self 。相反,传给 someFunctionWithNonescapingClosure(:) 的闭包是非逃逸闭包,也就是说它可以隐式地引用 self 。

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

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

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure {
            self.x = 100
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?()
print(instance.x)

自动闭包

把当做参数传递给函数的表达式,自动打包为闭包,然后把闭包作为参数来操作。

@autoclosure 将传入的表达式自动转为闭包。最终省了一个花括号。

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 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!"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,506评论 19 139
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,823评论 6 427
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    小迈克阅读 3,122评论 1 3
  • I: What: 保证沟通时接纳的心态,彼此情绪的愉快是达成一致意见的根本。 why: 角色不同,事情处理的习惯不...
    吾微Amy阅读 541评论 0 0
  • 说起喜欢做无用功的人,在职场简直太多见了,下班后迟回半小时,看起来给人的很努力,其实大多数是在做样子给领导看。节假...
    东风东风阅读 284评论 0 4

友情链接更多精彩内容