05-Swift闭包(Closures)

* 闭包 是自包含的函数代码块,可以在代码中被传递和使用。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;
}


四、闭包是引用类型


闭包是* 引用类型 *,在上述例子中,limingRunningTotalzhagnsanRunningTotal是常量,但这些常量是指向闭包,仍然还是可以增加其捕获的变量值。
 无论是将闭包赋值给一个常量还是变量,实际都是将常量或变量的值设置为对用闭包的引用。


五、非逃逸闭包


当一个闭包作为参数传入到一个函数中,但这个闭包在函数返回之后才被执行,该闭包称为从函数中* 逃逸 *。
 当你在定义函数,其参数是闭包,在参数之前使用@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环境

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 86.复合 Cases 共享相同代码块的多个switch 分支 分支可以合并, 写在分支后用逗号分开。如果任何模式...
    无沣阅读 1,347评论 1 5
  • 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代...
    穷人家的孩纸阅读 1,697评论 1 5
  • Swift 中的闭包是自包含的函数代码块,可以在代码中被传递和使用。类似于OC中的Block以及其他函数的匿名函数...
    乔克_叔叔阅读 522评论 1 3
  • 闭包是功能性自包含模块,可以在代码中被传递和使用。Swift中的闭包与 C 和 Objective-C中的 blo...
    AirZilong阅读 347评论 0 2
  • 绿玉呵红艳, 名唤美人蕉。 斑斓胜百花, 凄清比兰草。 细雨只增媚, 烈日总不凋。 微风袅袅过, 嫣然对我笑。 注...
    诗梦剑客阅读 232评论 0 0