iOS开发Swift

Swift闭包

闭包的概念


一门计算机语言如果要支持闭包,必须要有两个前提:支持函数类型,也就是说可以将函数作为参数进行传递,或者能够将函数作为返回值;支持函数嵌套。闭包是一种自包含的匿名函数代码块,它可以作为表达式、函数参数或者函数返回值。闭包表达式的运算结果是一种函数类型。闭包就是能够读取其他函数内部变量的函数,可以理解成“定义在一个函数内部的函数“

闭包:闭包就是能够读取其他函数内部变量的函数,可以理解成定义在一个函数内部的函数.
简单的说它就是一个代码块,用{}包起来,他可以用在其他函数的内部,将其他函数的变量作为代码块的参数传入代码块中.在Swift中多用于回调

闭包就是能够读取其他函数内部变量的函数解释说明

function f1(){
 
    var n=999;
 
    function f2(){
      alert(n);
    }
 
    return f2;
 
  }
 
  var result=f1();
 
  result(); // 999

// 在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的,子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
// 既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
// f2函数,就是闭包。
// 闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中(延长变量的声明周期,常驻内存)

闭包表达式的标准语法格式为:

// 闭包表达式的标准格式
{(参数列表) -> 返回值类型 in
    语句组
}
 
// 闭包形式
{(a: int, b: Int) -> Int in
    return a + b
}

闭包表达式的参数列表和函数的参数列表形式一样,其返回值类型也和函数的返回值类型相似。闭包与函数比较明显的一个差别是,返回值类型后面多了一个关键字in。

闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。

闭包的主要优化


  • 利用上下文推断参数和返回值类型
  • 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
  • 参数名称缩写
  • 尾随闭包语法

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


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

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

隐藏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就是错误的写法。

省略参数名


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

{$0 + $1}

使用闭包作为返回值


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

// 声明一个整型变量,用于接收闭包返回值
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类型,这就需要我们在闭包末尾大括号后面再跟上一对小括号,通过这个小括号为闭包传递参数。需要注意,这种写法在开发过程中拥有广泛的实际应用,尤其是在对控件进行懒加载时,用得非常的多!

闭包的应用场景


    闭包和block的应用场景是一样的.(当你觉得这种情况在oc中用block能实现的,那么在swift中用闭包也能实现)

  • 异步执行完成回调.
  • 控制器间回调
  • 自定义视图回调

闭包的三种模式


闭包有三种形式, 这里有一点需要注意的,一定要去执行调用闭包,否则里边代码不会执行

  • 1没有参数没有返回值的闭包(最简单的闭包)
  • 2,带参数没有返回值的闭包
  • 3,带参数带返回值的闭包
这里要注意的是,返回值需要进行操作,不然会报警告.

注意:用 in 去区分去分割函数的定义和实现,说通俗点就是分割 传参和后续操作的分割!!!

闭包回调


闭包回调,也就是说将闭包以参数的形式进行回调.

尾随闭包


尾随闭包是调用较简洁的写法,闭包表达式不仅可以作为函数的返回值进行传递,它还可以作为函数的参数进行传递。但是,如果闭包表达式很长,就会导致整个函数的参数列表也非常的长,这样很容易影响程序的阅读性。不过,好在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 3.0之后,传递闭包到函数中的时候,系统会默认为非逃逸闭包类型(NonescapingClosures)@noescaping,逃逸闭包在闭包前要添加@escaping关键字。

1.从生命周期看两者区别:

  • 非逃逸闭包的生命周期与函数相同:
1,把闭包作为参数传给函数;
2,函数中调用闭包;
3,退出函数。结束
  • 逃逸闭包的生命周期
1,闭包作为参数传递给函数;
2,退出函数;
3,闭包被调用,闭包生命周期结束

即逃逸闭包的生命周期长于函数,函数退出的时候,逃逸闭包的引用仍被其他对象持有,不会在函数结束时释放

2.经常使用逃逸闭包的2个场景:

  • 异步调用: 如果需要调度队列中异步调用闭包,比如网络请求成功的回调和失败的回调,这个队列会持有闭包的引用,至于什么时候调用闭包,或闭包什么时候运行结束都是不确定,上边的例子。
  • 存储: 需要存储闭包作为属性,全局变量或其他类型。

3.通过一个实例代码来讲解逃逸闭包和非逃逸闭包的区别

class HttpTool: NSObject {
    func loadData(callBack:(String)->())  {   ///(1)
        callBack("非逃逸闭包")              ///(2)
    }                                     ///(3)
}

代码执行顺序(1),(2),(3)

class HttpTool: NSObject {
    func loadData(callBack:@escaping((String)->()))  {   ///(1)
        DispatchQueue.global().async {
            DispatchQueue.main.async {
                callBack("非逃逸闭包")              ///(2)
            }
        }
    }                                             ///(3)
}

代码执行顺序:(1),(3),(2)

逃逸闭包前面添加@escaping关键字,这里闭包的生命周期不可预知。