05-Swift闭包(Closures)

2018-10-20  本文已影响9人  王梓懿_1fbc
函数分全局和嵌套函数,而函数其实是特殊的闭包,闭包采取如下三种形式之一:
Swift的闭包表达式的拥有简洁风格,并鼓励在一下常见场景中进行语法优化:

一、闭包表达式

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"]
// 上面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表示闭包的参数和返回值类型定义已经完成,闭包的代码模块即将开始;

// 即省略参数类型和返回值类型
let sortNames = names.sort({ str1, str2 in return str1 > str2 });
// 即省略return关键字
let sortNames = names.sort({ str1, str2 in str1 > str2 });
// 即参数缩写
let sortNames = names.sort({ $0 > $1});
// 运算符函数的运用
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!

注:xcode7.3环境

作者:西门奄
链接:https://www.jianshu.com/u/77035eb804c3
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

上一篇 下一篇

猜你喜欢

热点阅读