Day7 闭包(Closures)

2017-06-08  本文已影响0人  平凡之路561

本页包含内容:
• 闭包表达式
• 尾随闭包
• 值捕获
• 闭包是引用类型
• 逃逸闭包
• 自动闭包

    闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。
    闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
     
     在函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
     • 全局函数是一个有名字但不会捕获任何值的闭包
     • 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
     • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
     
     Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
     • 利用上下文推断参数和返回值类型
     • 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字 • 参数名称缩写
     • 尾随闭包语法

1、闭包表达式

闭包表达式参数可以是 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 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:

<pre>reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
print(reversedNames)</pre>

因为排序闭包函数是作为 sorted(by:) 方法的参数传入的,Swift 可以推断其参数和返回值的类型。
sorted(by:) 方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool 类型的函数。
这意味着 (String, String) 和 Bool 类型并不需要作为闭包表达式定义的一部分。
因为所有的类型都可以被正确推断,返回箭头( -> )和围绕在参数周围的括号也可以被省略:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

2、尾随闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
在使用尾随闭包时,你不用写出它的参数标签:

    <pre>func someFunctionThatTakesAClosure(closure: () -> Void) {
        // 函数体部分
    }
    // 以下是不使用尾随闭包进行函数调用
    someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分 
    })
    // 以下是使用尾随闭包进行函数调用 
    someFunctionThatTakesAClosure() {
    // 闭包主体部分
    }</pre>

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、闭包的用法 **

<pre>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")
}
}</pre>
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)}}

** 4、解决循环引用的方式**

    /*

** 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);
            }
        }
    }
上一篇 下一篇

猜你喜欢

热点阅读