Swift中的闭包

96
Enrica_Shi
2017.05.23 11:46* 字数 1469

  1、闭包的概念

  一门计算机语言如果要支持闭包,必须要有两个前提:支持函数类型,也就是说可以将函数作为参数进行传递,或者能够将函数作为返回值;支持函数嵌套。闭包是一种自包含的匿名函数代码块,它可以作为表达式、函数参数或者函数返回值。闭包表达式的运算结果是一种函数类型。闭包表达式的标准语法格式为:

// 闭包表达式的标准格式
{(参数列表) -> 返回值类型 in
    语句组
}

// 闭包形式
{(a: int, b: Int) -> Int in
    return a + b
}

  闭包表达式的参数列表和函数的参数列表形式一样,其返回值类型也和函数的返回值类型相似。闭包与函数比较明显的一个差别是,返回值类型后面多了一个关键字in。我们知道在Swift中,编译器可以做的事情有很多,比如说类型推断。下面我们就利用编译器的类型推断对上面的闭包形式进行简化。

  2、利用类型推断对闭包进行简化

  在Swift中,编译器可以根据上下文的环境推断出参数和返回值的类型,比如说像上面那个闭包标准形式,完全可以利用编译器特性将其简化为:

// 闭包形式
{(a, b) in
    return a + b
}

// 省略参数列表小括号,并且将其写成一行
{a, b in return a + b}

  3、隐藏return关键字

  如果闭包内部语句组中只有一条语句,比如说像上面的return a + b,那么我们就可以将返回语句中的return关键字给省略掉:

// 省略返回语句中的关键字return
{a, b in a + b}

  需要特别强调一下,只有当关键字in后面的语句组只有一条返回语句时,你才能将关键字return给省略掉。如果关键字in后面有多条语句,那么关键字return则不能省略。比如说,像{a, b in var c = 0; a + b}这种关键字in后面有多条语句的情况,省略return就是错误的写法。

  4、省略参数名

  上面的闭包已经非常简洁了,但是还不是最简洁的。其实还可以再极端一点,将参数名也给省略掉。我们可以用$0、$1、$3...$n来指定闭包中的参数,其中$0表示闭包参数列表中的第一个参数,$1表示闭包参数列表中的第2个参数...依此类推。除了参数列表可以省略之外,关键字in也可以省略,上面的闭包可以简写为:

{$0 + $1}

  像上面这种形式,我之前在《Swift中常用的数据结构(上)》中讲Dictionary时就已经用过:

// 初始化一个字典
var provences = ["SH" : "Shanghai", "BJ" : "Beijing", "HB" : "Hubei", "HN" : "Hunan", "GD" : "Guangdong"]

// 只遍历字典中的键
for provenceAbbr in provences.keys {
    print("字典中各省份的简称是:\(provenceAbbr)")
}

// 对字典中的键值对进行重新排序,然后返回一个数组
let sortedArrFromDict = provences.sorted(by: {$0.0 < $1.0})

// 遍历数组
for (key) in sortedArrFromDict.map({$0.0}) {
    print("字典中的键分别为:\(key)")
}

  5、使用闭包作为返回值

  闭包表达式本质上是函数类型,函数里面有函数返回值,那么闭包里面肯定也有闭包返回值。比如说,我们举一个例子:

// 声明一个整型变量,用于接收闭包返回值
let num1: Int = {(a: Int, b: Int) -> Int in
                    return a + b
                 }(10, 5)
print(num1)  // 结果为15

let num2: Int = {$0 - $1}(10, 5)
print(num2)  // 结果为5

  在上面的代码中,num1为Int类型,因此我们不能直接将闭包表达式赋值给它。但是这个闭包是有返回值的,而且它的返回值刚好也是Int类型,这就需要我们在闭包末尾大括号后面再跟上一对小括号,通过这个小括号为闭包传递参数。需要注意,这种写法在开发过程中拥有广泛的实际应用,尤其是在对控件进行懒加载时,用得非常的多!就以我们在《Swift基础知识补充(二)》中讲值绑定时那个下载网络图片的代码为示例,我们用闭包对它进行改写:

// 对imageView控件进行懒加载
fileprivate lazy var imageView: UIImageView = {

    // 创建imageView控件
    let imageView = UIImageView()
    
    // 设置imageView控件的尺寸
    imageView.frame = CGRect(x: 0, y: 0, width: 200, height: 300)
    
    // 将imageView控件返回
    return imageView
}()

override func viewDidLoad() {
    super.viewDidLoad()
    
    // 先校验图片url地址是否正确
    guard let url = URL(string: "https://gss0.baidu.com/-fo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/0b55b319ebc4b745359202e2c8fc1e178a82153b.jpg") else {
        return
    }
    
    // 再校验图片有没有下载成功
    guard let imageData = try? Data(contentsOf: url) else {
        return
    }
    
    // 将图片控件添加到父控件中
    view.addSubview(imageView)

    // 设置imageView的位置
    imageView.center = view.center

    // 设置imageView的图片
    imageView.image = UIImage(data: imageData)
}

  出于性能方面的考虑,有很多只需要设置一次的控件属性代码,都会放在懒加载后面的闭包中进行设置,所以这种语法一定要掌握。

  6、尾随闭包

  闭包表达式不仅可以作为函数的返回值进行传递,它还可以作为函数的参数进行传递。但是,如果闭包表达式很长,就会导致整个函数的参数列表也非常的长,这样很容易影响程序的阅读性。不过,好在Swift函数支持尾随闭包。我们先来看一个具体的示例,然后再来解释什么是尾随闭包:

// 尾随闭包
func calculate(opt: String, closureP: (Int, Int) -> Int) {
    
    switch opt {
    case "+":
        print("10 + 5 = \(closureP(10, 5))")
    default:
        print("10 - 5 = \(closureP(10, 5))")
    }
}

// 函数调用1:调用的时候将闭包作为参数传递给函数
calculate(opt: "+", closureP: {(a: Int, b: Int) -> Int in return a + b})

// 函数调用2:调用的时候将闭包表达式移到函数小括号后面
calculate(opt: "+") { (a: Int, b: Int) -> Int in
    return a + b
}

// 函数调用3:对函数调用2进行简化
calculate(opt: "+"){$0 + $1}

  现在我们来解释一下上面的代码,看看到底什么叫做尾随闭包。我们定义了一个calculate()函数,这个函数有两个参数,第一个参数opt是String类型,第二个参数closureP是一个闭包类型。第一次调用calculate()函数时,我们是按照正常调用函数的步骤,将opt和closureP这两个参数分别传入。但是,在第二次调用calculate()函数时,我们只给它传递了第一个参数opt,而第二个参数closureP是直接挪到了calculate()函数小括号的外面,像这种写法就是尾随闭包

  尾随闭包是指一个书写在函数小括号后面的闭包表达式,Swift函数支持将其作为最后一个参数进行调用。不过,需要注意的是,在使用尾随闭包时,闭包必须是函数参数列表中的最后一个参数,否则就不能使用尾随闭包的形式。

Swift基础
Web note ad 1