本页包含内容:
• 闭包表达式
• 尾随闭包
• 值捕获
• 闭包是引用类型
• 逃逸闭包
• 自动闭包
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
在函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
• 全局函数是一个有名字但不会捕获任何值的闭包
• 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
• 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
• 利用上下文推断参数和返回值类型
• 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字 • 参数名称缩写
• 尾随闭包语法
1、闭包表达式
- 闭包表达式语法
闭包表达式语法有如下的一般形式:
{ (parameters) -> returnType in
statements
}
闭包表达式参数可以是 in-out 参数,但不能设定默认值。
也可以使用具名的可变参数(注:但是如果可变参数不放在参数列表的最后一位的话,调用闭包的时时编译器将报错。)元组也可以作为参数和返回值。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
//闭包类型:(String, String)->Bool
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
闭包的函数体部分由关键字 in 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
print(reversedNames)
- 定义一个求和闭包
//闭包类型:(Int,Int)->(Int)
let add:(Int,Int)->(Int) = {
(a,b) in
return a + b;
}
//执行闭包,相当于调用函数
let result = add(1100, 200);
//打印闭包返回值
print("result=(result)");
参数和需执行的代码(code)用 关键字“in”隔开,如果闭包没有参数, “ () in”可以直接省略:
1、{
(参数1,参数2) in
//code
}
2、{
//code
}
- 根据上下文推断类型
因为排序闭包函数是作为 sorted(by:) 方法的参数传入的,Swift 可以推断其参数和返回值的类型。
sorted(by:) 方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool 类型的函数。
这意味着 (String, String) 和 Bool 类型并不需要作为闭包表达式定义的一部分。
因为所有的类型都可以被正确推断,返回箭头( -> )和围绕在参数周围的括号也可以被省略:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
单表达式闭包隐式返回
单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
在这个例子中,sorted(by:) 方法的参数类型明确了闭包必须返回一个 Bool 类型值。
因为闭包函数体只包含 了一个单一表达式( s1 > s2 ),该表达式返回 Bool 类型值,因此这里没有歧义, return 关键字可以省略。-
参数名称缩写
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0 , $1 , $2 来顺序调用闭包的参数,以此类推。
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。
in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
reversedNames = names.sorted(by: { $0 > $1 } )
在这个例子中,$0 和 $1表示闭包中第一个和第二个 String 类型的参数。你也可以用关键字typealias先声明一个闭包的数据类型:
声明一个闭包类型 AddBlock
typealias AddBlock = (Int,Int)->(Int);
let add:AddBlock = {
(a,b) in
return a + b;
}
let result = add(1100, 200);
print("result=(result)"); -
运算符方法
Swift 的 String 类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。
而这正好与sorted(by:) 方法的参数需要的函数类型相符合。
因此,你可以简单地传递一个大于号,Swift 可以自动推断你想使用大于号的字符串函数实现:
reversedNames = names.sorted(by: >)
print(reversedNames);
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
2、尾随闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
在使用尾随闭包时,你不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
sorted(by:) 方法参数的字符串排序闭包可以改写为:
reversedNames = names.sorted() { $0 > $1 }
如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:
reversedNames = names.sorted { $0 > $1 }
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用
举例来说,Swift 的 Array 类型有一个 map(:) 方法,这个方法获取一个闭包表达式作为其唯一参数。
该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。
当提供给数组的闭包应用于每个数组元素后,map(:) 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
下例介绍了如何在 map(_:) 方法中使用尾随闭包
将 Int 类型数组 [16, 58, 510] 转换为包含对应 String 类型的值的数组
["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]
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)
闭包表达式在每次被调用的时候创建了一个叫做 output 的字符串并返回。
其使用求余运算符( number % 10 )计算最后一位数字并利用 digitNames 字典获取所映射的字符串。这个闭包能够用于创建任意正整数的字符 串表示。
闭包简写:
1.如果没有参数, 没有返回值, in和in之前的东西可以省略
2.如果闭包是函数的最后一个参数, 可以写在()后面 -- 尾随闭包
3.如果只有一个闭包参数, 那么()也可以省略 -- 尾随闭包
** 3、闭包的用法 **
- (1) 两个类之间的通信
CustomView类中代码:
class CustomView: UIView {
//声明一个属性btnClickBlock,type为闭包可选类型
//闭包类型:()->() ,无参数,无返回值
var btnClickBlock:(()->())?
//重写 init(frame: CGRect)构造函数
override init(frame: CGRect) {
super.init(frame:frame)
//创建按钮
let btn = UIButton(frame: CGRect(x: 15, y: 15, width: 80, height: 32))
btn.setTitle("按钮", for: .normal)
btn.backgroundColor = UIColor.blue
//绑定事件
btn.addTarget(self, action: #selector(CustomView.btnClick), for: .touchDown)
//添加
addSubview(btn)
}
//按钮点击事件函数
func btnClick(){
if self.btnClickBlock != nil {
//点击按钮执行闭包
//注意:属性btnClickBlock是可选类型,需要先解包
self.btnClickBlock!()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Controller类中代码:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//创建CustomView对象
let cutomeView = CustomView(frame: CGRect(x: 50, y: 50, width: 200, height: 200));
//给cutomeView的btnClickBlock闭包属性赋值
cutomeView.btnClickBlock = {
// () in 无参数可以省略
//当按钮被点击时会执行此代码块
print("按钮被点击");
}
cutomeView.backgroundColor = UIColor.yellow;
//添加到控制器view上
self.view.addSubview(cutomeView)}}
-
(2)异步回调(callBack)
// 定义一个网络请求函数// - parameter urlString: 请求接口 String // - parameter succeed: 成功的回调 可选闭包 // - parameter failure: 失败的回调 可选闭包 func requestData(url:String, success:@escaping ((Any?)->(Void)), failure:((Any?)->(Void))?) { //发送网络请求 let session = URLSession.shared; let request = URLRequest(url: URL(string:url)!) let task = session.dataTask(with: request) { (data, respon, error) in if error == nil { //请求成功,执行成功的回调,并把数据传递出去 success(data); }else{ //请求失败,执行失败的回调,并把错误传递出去 failure?(error); } } task.resume(); } //调用函数requestData函数 requestData(url: "http://www.baidu.com", success: { (data) -> (Void) in print("请求成功") }) { (error) -> (Void) in print("请求失败") }
** 4、解决循环引用的方式**
方案一:使用weak
weak var weakSelf = self
//调用函数requestData函数
requestData(url: "http://www.baidu.com", success: {(data) -> (Void) in
print("请求成功")
weakSelf?.view.backgroundColor = UIColor.blue
}) { (error) -> (Void) in
print("请求失败")
}方案二:和方案一类型,只是书写方式更加简单,可以写在闭包中,并且在闭包中用到的self都是弱引用
requestData(url: "http://www.baidu.com", success: { weak self -> (Void) in
self?.view.backgroundColor = UIColor.red
print("请求成功")
}) { (error) -> (Void) in
print("请求失败")
}方案三:使用关键字unowned,从行为上来说 unowned 更像OC中的 unsafe_unretained,
// unowned 表示:即使它原来引用的对象被释放了,仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil
requestData(url: "http://www.baidu.com", success: { unowned self -> (Void) in
self.view.backgroundColor = UIColor.red
print("请求成功")
}) { (error) -> (Void) in
print("请求失败")
}
/*
** 5、逃逸闭包 **
当闭包作为一个参数传递到函数时,我们知道它一般是用于函数内部的异步回调,闭包是等异步任务完成以后才调用,而函数是会很快执行完毕并返回的,所以闭包它需要逃逸,以便稍后的回调。
逃逸闭包一般用于异步函数的回调,比如网络请求成功的回调和失败的回调。语法:在函数的闭包行参前加关键字 “@escaping”
。
那么没有出现关键字“@escaping”,你可以拉回去看下成功回调或失败的回调,类型是 “((Any?)->(Void))?”
,后面带了个“?”,这是闭包可选类型,并不是闭包类型,所以无需关键字“@escaping”。
假设成功和失败的回调要弄成闭包类型,而你又要异步使用的话,那就要在形参前面加关键字,
*/
func requestDataA(urlString:String,succeed: @escaping (Any?)->(Void),failure:@escaping (Any?)->(Void)){
let request = URLRequest(url: URL(string: urlString)!);
//发送网络请求
NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue()) { (_, data, error) in
if error == nil {
//请求成功,执行成功的回调,并把数据传递出去
succeed(data);
}else{
//请求失败,执行失败的回调,并把错误传递出去
failure(error);
}
}
}