* 闭包 是自包含的函数代码块,可以在代码中被传递和使用。swift中的闭包和Objective-C中的代码块(block)以及其他一些编程语言中的匿名函数类型。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。这所谓的闭合并包裹这些常量和变量,即是 闭包 *。Swift中会自动处理在捕获过程中涉及到的所有内存操作。
函数分全局和嵌套函数,而函数其实是特殊的闭包,闭包采取如下三种形式之一:
- 全局函数是一个有名字,但不会捕获任何值的闭包;
- 嵌套函数是一个有名字,并可以捕获其封闭函数区域内的值的闭包;
- 闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值的匿名闭包;
(即全局函数、嵌套函数也都是闭包!)
Swift的闭包表达式的拥有简洁风格,并鼓励在一下常见场景中进行语法优化:
- 利用上下文推断参数和返回值类型;
- 隐式返回单表达式闭包,即单表达式闭包可以省略
return
关键字; - 参数名称缩写;
- 尾随闭包语法;
一、闭包表达式
* 嵌套函数 是一个在复杂函数中方便进行命名和定义自包含代码模块的方式。 闭包表达式 *是一种利用简洁语法构建内联闭包的方式。
-
sort
方法。Swift标准库中提供了名为sort
的方法,会根据所提供的用于排序的闭包函数,将已知类型数组中的值进行排序。排序完成后,sort(_:)
方法会返回一个与原数组大小相同,包含同类型且元素已是排序好的新数组,而原数组不会被sort(_:)
方法修改。
sort
方法的使用:sort(_:)
方法接受一个闭包,该闭包需要传入与数组元素类型相同的两个值,并返回一个布尔类型的值,这是用于表明当排序结束后传入的第一个参数是在第二个参数的前面还是后面。如果第一个参数值出现在第二参数值前面,排序闭包需要返回true
,否则返回false
。
// 数组对应: 张三、李四、王五、赵六、田七
let names = ["zhansan","lisi","wangwu","zhaoliu","tianqi"];
// 排序方法
func mySort(str1:String, str2:String) -> Bool {
// 即str1是在str2的前面,返回true
return str1 > str2;
}
// 调用sort方法排序
var sortNames = names.sort(mySort);
print("排序前:\(names)");
print("排序后:\(sortNames)");
输出结果:
排序前:["zhansan", "lisi", "wangwu", "zhaoliu", "tianqi"]
排序后:["zhaoliu", "zhansan", "wangwu", "tianqi", "lisi"]
- 闭包表达式语法。在这种可以使用常量、变量和
inout
类型作为参数,不能提供默认值。也可以在参数列表的最后使用可变参数。另外,元组也可以作为参数和返回值:
// 上面mySort(_:_:)函数可以写成闭包表达式
let names = ["zhansan","lisi","wangwu","zhaoliu","tianqi"];
let sortNames = names.sort({ (str1:String, str2:String) -> Bool in
return str1 > str2;
});
// 代码简单,改写为一行
// let sortNames = names.sort({ (str1:String, str2:String) -> Bool in return str1 > str2; });
print("排序前:\(names)");
print("排序后:\(sortNames)");
输出结果:
排序前:["zhansan", "lisi", "wangwu", "zhaoliu", "tianqi"]
排序后:["zhaoliu", "zhansan", "wangwu", "tianqi", "lisi"]
闭包表达式语法一般形式
{ (parameters) -> returnType in
statements
}
// 注意1: 参数和返回值类型都是在大括号内,而不是大括号外;
// 注意2: 闭包中代码部分由关键字in
引入,即是in
表示闭包的参数和返回值类型定义已经完成,闭包的代码模块即将开始;
- 根据上下文推断类型。在上面排序中使用到
sort(_:)
方法,而该方法其参数必须是(String, String) -> Bool
类型,也就意味(String, String)
和Bool
并不需要作为闭包表达式定义的一部分。因为类型都可以被正确推断:
// 即省略参数类型和返回值类型
let sortNames = names.sort({ str1, str2 in return str1 > str2 });
- 单表达式闭包隐式返回。即单行表达式闭包可以通过省略
return
关键字来隐式返回单行表达式的结果:
// 即省略return关键字
let sortNames = names.sort({ str1, str2 in str1 > str2 });
- 参数名称缩写。Swift自动为内联闭包提供了参数名称缩写功能,你可以用
$0
、$1
、$2
来顺序调用闭包的参数,以此类推。另外,类型是可以通过上下文推断,所以in
关键字也同样可以省略:
// 即参数缩写
let sortNames = names.sort({ $0 > $1});
- 运算符函数。swift的
String
类型定义了关于大于号>
的字符串实现,其作为函数接收两个String
类型的参数并返回Bool
类型的值,因此上面排序就还可以写的更简短(简短的让你抓狂,别怀疑,绝对是可以运行的,也是绝对没问题的):
// 运算符函数的运用
let sortNames = names.sort(>);
// 注: >即从大到小,若是换为<即是从小到大
二、尾随闭包
- 若闭包表达式很长作为最后一个参数传递给参数,可以使用* 尾随闭包 *来增强函数的可读性:
// 参数缩写
//let sortNames = names.sort({ $0 > $1});
// 尾随闭包
//let sortNames = names.sort(){ $0 > $1 };
// 尾随闭包也可以将()省略
let sortNames = names.sort { $0 > $1 };
- 当闭包非常长以至于不能在一行中进行书写时,尾随闭包就非常有用:
// 将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 str = numbers.map {
// 参数为number,返回值类型String
(var number) -> String in
// 具体代码区域
var output = "";
while number > 0 { // 取出对应位数的数值
// 从个位开始,并找到对应英文,最后拼接
output = digitNames[number % 10]! + output;
// 从个位开始截去
number = number / 10;
}
return output;
}
print(numbers);
print(str);
输出结果:
[16, 58, 510]
["OneSix", "FiveEight", "FiveOneZero"]
Array
类型中map(_:)
方法: 获取一个闭包表达式作为其唯一参数。该闭包会为数组中的每一个元素调用一次,并返回该元素所映射的值,而具体的映射方式和返回值类型由闭包来指定。
三、捕获值
闭包可以在其被定义的上下文中* 捕获 常量或变量。即定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包的代码区域中引用和修改这些值。
swift中,可以捕获值的闭包其最简单形式就是 嵌套函数 ,即是定义在其他函数的函数体内的函数, 嵌套函数 *可以捕获其外部函数所有的参数以及定义的常量和变量。
/** 实现计步功能*/
// 返回类型: (Int) -> Int
func makeRunningTotal() -> (Int) -> Int {
var runningTotal = 0
// 嵌套函数,特殊闭包,参数amount是一个增量
func addFunc(amount:Int) -> Int {
// 捕获runningTotal
runningTotal += amount
return runningTotal
}
// 注意: 不要写成addFunc(),这是函数调用,而不是返回函数
// 即makeRunningTotal将addFunc作为闭包返回
return addFunc;
}
// 李明
let liming = makeRunningTotal();
print("李明-总步数: \(liming(10))");
print("李明-总步数: \(liming(20))");
print("李明-总步数: \(liming(30))");
// 张三
let zhagnsan = makeRunningTotal();
print("张三-总步数: \(zhagnsan(50))");
print("张三-总步数: \(zhagnsan(100))");
print("张三-总步数: \(zhagnsan(200))");
输出结果:
李明-总步数: 10
李明-总步数: 30
李明-总步数: 60
张三-总步数: 50
张三-总步数: 150
张三-总步数: 350
单独看嵌套函数,会发现函数体中使用的变量
runningTotal
不是在函数体内部定义的;addFunc()
函数就是外围函数中捕获了runningTotal
的引用;捕获引用保证runningTotal
在调用完addFunc()
后不会消失,并保证在下一次执行addFunc()
函数的时候,runningTotal
依旧存在;
func addFunc(amount:Int) -> Int {
runningTotal += amount;
return runningTotal;
}
四、闭包是引用类型
闭包是* 引用类型 *,在上述例子中,limingRunningTotal
和zhagnsanRunningTotal
是常量,但这些常量是指向闭包,仍然还是可以增加其捕获的变量值。
无论是将闭包赋值给一个常量还是变量,实际都是将常量或变量的值设置为对用闭包的引用。
五、非逃逸闭包
当一个闭包作为参数传入到一个函数中,但这个闭包在函数返回之后才被执行,该闭包称为从函数中* 逃逸 *。
当你在定义函数,其参数是闭包,在参数之前使用@noescape
标注,即表明该闭包不允许"逃逸"出这个函数。而这个@noescape
闭包标注,就是是告诉编译器这个闭包的生命周期是在它所在函数体中的。
而在默认情况,即没有加上@noescape
标注的,闭包是可以"逃逸"出函数的。
- 非逃逸闭包:
/** 下载数据的函数 - 非逃逸闭包
参数接收一个闭包
@noescape即表明该闭包是属于"非逃逸闭包"
*/
func downloadData1 (@noescape closure:(Void) -> Void) {
// 闭包closure的调用,就是该闭包closure只有在downloadData1函数中执行调用
print("即将开始下载...");
closure();
print("已经开始下载...");
}
// 调用下载函数
downloadData1() {
print("正在下载QQ...")
sleep(2); // 延时操作,延时2秒,用于演示正在下载QQ
};
/** 效果
先打印"即将开始下载...",
再打印"正在下载QQ...",
2秒过后,
最后才打印"已经开始下载..."
即表示闭包是在downloadData1函数中执行的;
*/
输出结果:
即将开始下载...
正在下载QQ...
已经开始下载...
- 逃逸闭包:
// 具体操作处理者
// 处理者这里是一个数组,即要处理的事件数组
var downLoadHandler:[(Void)->(Void)] = [];
/** 下载数据的函数 - 逃逸闭包
参数接收一个闭包
没有@noescape,即表明该闭包是属于"逃逸闭包"
*/
func downloadData2 (closure:(Void) -> Void) {
// 将具体操作添加到处理者,而不需要管什么时候处理
// 只添加,但闭包是没有执行的!!!
// 闭包closure不是在downloadData2中执行,即是"逃逸闭包"
print("即将添加到处理者");
downLoadHandler.append(closure);
print("已经添加到处理者");
}
// 添加下载QQ操作
downloadData2() {
print("正在下载QQ...");
sleep(2); // 延时操作,用于演示正在下载QQ
};
// 添加下载xcode操作
downloadData2 {
print("正在下载xcode...");
sleep(2); // 延时操作,用于演示正在下载xcode
}
// 处理者downLoadHandler,处理事件
for (index,closure) in downLoadHandler.enumerate() {
// 具体处理事件
print("正在处理事件:\(index)");
closure();
print("处理完成事件:\(index)");
}
输出结果:
即将添加到处理者
已经添加到处理者 // 即是添加"下载QQ"闭包
即将添加到处理者
已经添加到处理者 // 即是添加"下载xcode"闭包
正在处理事件:0 // 即是"下载QQ"闭包的调用
正在下载QQ...
处理完成事件:0
正在处理事件:1 // 即是"下载xcode"闭包的调用
正在下载xcode...
处理完成事件:1
注: 关于* 逃逸闭包 ,更多都会用于异步操作相关的封装。即在异步操作开始后就立即返回,而 逃逸闭包 *是在异步操作结束之后才会被调用。
六、自动闭包
* 自动闭包 *是一种自动创建的闭包,用于包装传递给函数作为参数的表达式,而这种闭包不接受任何参数,当它被调用后,会返回包装在其中表达式的字。
- 自动闭包语法可以将一段具体操作封装成一个变量或常量,方便使用操作:
// 将问候操作以闭包形式赋值给一个常量
let speakClosures = {
print("hello world!");
print("hello swift!");
}
// 当在需要问候的时候,只需要调用speakClosures即可
speakClosures();
// 作为参数传递
func testFunc(closures:()->()) {
// 调用
closures();
}
testFunc(speakClosures);
输出结果:
hello world!
hello swift!
hello world!
hello swift!