其实这个闭包可以看做是匿名的函数。
我们先来回想一下函数作为参数的情况
//定义一个函数,它最后的参数是一个函数类型
func doMath(first: Int, second: Int, mathFunc: (Int, Int) -> Int) {
print("mathFunc =",mathFunc(first,second))
}
//定义一个函数,它有两个整形参数,并有一个整形返回值
func add(first: Int, _ second: Int) -> Int{
return first + second
}
//调用第一个函数,将第二个函数作为参数传入
doMath(1, second: 3, mathFunc: add)
//打印结果为 mathFunc = 4
如果我们想用doMath实现两个数相减的方法,那么必须再写定义一个sub函数,然后将其作为参数传入。这样在功能多了之后会显得很麻烦,一堆函数,而所以有了闭包这个概念。
闭包的语法
{ (参数列表) -> 返回类型 in
//闭包体
}
有了闭包,我们可以将上面的代码改为
//定义一个函数,它最后的参数是一个函数类型
func doMath(first: Int, second: Int, mathFunc: (Int, Int) -> Int) {
print("mathFunc =",mathFunc(first,second))
}
doMath(1, second: 3, mathFunc: {(f: Int, s: Int) -> Int in
return f + s
})
还是很麻烦是吧? 别忘了Swift有类型推断功能,所以我们可以继续简化上面的闭包部分代码
doMath(1, second: 3, mathFunc: {f, s in
return f + s
})
对应只有一行代码的闭包,return关键字还可以省略
doMath(1, second: 3, mathFunc: {f, s in f + s })
此外,闭包对参数提供了默认名字,依次为 $0,$1,$2....所以上面的闭包仍可以简化
doMath(1, second: 3, mathFunc: {$0 + $1 })
对于闭包在参数列表最后一项的情况,可以将闭包写到小括号外部,并且可以省略掉外部参数名
doMath(1, second: 3){
var f = $0 + 1
return f + $1
}
Autoclosures
姑且叫自动打包吧。用大括号括起来就好,编译器自动判断这个大括号里面的是什么返回类型。但是有时候不准确,需要自己写。下面是这个概念的解释,其实也是一种定义闭包变量的方法。
var t = {
return 1
}
print(t())
定义了一个Void->Void类型的闭包。因为没有参数,所以可以省略参数列表和in关键字。如果有参数的话,就不能省略in关键字。
var b: Void->Int = { //定义了一个类型为 Void->Int的闭包
var i = 1
i++
print(i)
return i
}
因为闭包其实就是函数,调用这个闭包就和调用函数一样。但是有区别的就是闭包都是没有外部外部参数名,调用的时候不要把内部参数名但做外部参数名使用。
有时候函数需要传递一个闭包的时候,可以在调用的时候使用大括号将一段代码生成为闭包。
var b: Void->Int = {
var i = 1
return i
}
func doClosures(c: Void->Void) {
c()
}
doClosures({b()}) //虽然b是一个Void->Int的闭包,但是其调用再封装之后变为了Void->Void的闭包
doClosures({
var i = 3
i++
print(i)
})
此外,可以在函数参数列表里面使用@autoclosure关键字,这样就不用使用大括号封装了。但是对于多句的代码情况不行(上面的第二种),有时候自动封装也会出错,比如用上面的第一种情况,它把b()看做了Int,然后报错。需要将返回类型重新定义一下
var b: Void->Void = {
var i = 1
i++
print(i)
// return i
}
func doClosures(@autoclosure c: Void->Void) { //或者不改b的类型,将这里的c的类型改为 Void->Int也可以
c()
}
doClosures(b())
如果想要自动封装的闭包可以在doClosures函数的作用域以外使用,那么加上escaping关键字。这个关键字只能用在@autoclosure后面。
var b: Void->Void = {
var i = 1
i++
print(i)
}
var t: (Void->Void)?
func doClosures(@autoclosure(escaping) c: Void->Void) {
c()
t = c //将自动封装的c赋值给外部变量t
}
doClosures(b())
t!()
闭包的值捕获
在生成一个闭包的时候,闭包会将它用到的参数和变量都保存一份。提醒一下,其实闭包就是函数。
func giveMeFunc2(step: Int) -> (Void -> Int)? {
var total = 0
func add() -> Int { total += step; return total }
return add
}
上面的函数里面生成了嵌套函数,通过输入不同的符号,返回不同的函数。这里有两个变量需要注意,一个是total,一个是step。当生成嵌套函数的时候,嵌套函数会将这两个变量都copy一份,然后保存起来。下面是对上面代码的一个使用
var f1 = giveMeFunc2(1)! //得到一个函数,它会将传入的参数累加,并且每次调用都会加上一次step
print("f1=",f1()) // 1
print("f1=",f1()) // 2
var f2 = giveMeFunc2(2)! //得到一个函数,它会将传入的参数累加,并且每次调用都会减去一次step
print("f2=",f2()) // 2
print("f2=",f2()) // 4
print("f2=",f1()) // 3
可以看到,f1和f2的total和step是不会相互干涉的。
再来看看这个值捕获的时间,看下面代码。这里可以看到,值捕获是发生在返回之前。这个和OC的block是一样的。
func giveMeFunc2(step: Int) -> (Void -> Int)? {
var total = 0
func add() -> Int { total += step; return total }
print("before +100",add()) // total = 0
total += 100
print("after +100",add()) // total = 100
return add
}
var f1 = giveMeFunc2(1)! //得到一个函数,它会将传入的参数累加,并且每次调用都会加上一次step
print("f1=",f1()) // 103
print("f1=",f1()) // 104
看到这里,可能大家会以为这个值捕获和OC的block差不多,但是其实差远了。这个值捕获的时间很有区别。这里明显的一点就是我们在函数内部改变外部变量total的时候,没有加任何修饰符,OC里面必须加上__block,要么就是对全局变量进行修改。
我们先看一段OC代码
int t =1;
int(^b)() = ^() { return t; };
t = 3;
NSLog(@"%d",b()); //输出1,理由就不多说了。
假如我们把t改为__block。那么将会输出3。改为static同样的效果。
__block int t =1;
int(^b)() = ^() { return t; };
t = 3;
NSLog(@"%d",b()); //3
来看OC和swift中两段很类似的代码
//OC
typedef int(^BLOCK)(void);
BLOCK OCFunc (int step) {
__block int total = 0;
BLOCK b = ^() { total +=step; return total; };
step = 100;
NSLog(@"before +100,%d",b()); //1
total +=100;
NSLog(@"after +100,%d",b()); //102
return b;
}
//在main方法里面调用
BLOCK b = OCFunc(1);
NSLog(@"%d",b()); // 103
NSLog(@"%d",b()); // 104
//Swift
func swiftFunc(var step: Int) -> Void -> Int{
var total = 0
let b: Void -> Int = { Void in total += step; return total }
step = 100;
print("before +100,",b()) // 100
total+=100 // total = 200
print("after +100,",b()) //300
return b
}
let d = swiftFunc(1)
print("d=",d()) //400
print("d=",d()) //500
这里可以看到,OC中的step在block定义的时候就绑定了,后面在更改step的值也不影响block。但是在swift中,step仍然是可以改变的,直到step离开作用域后,闭包才将其捕获。
如果要OC中产生同样的效果,只需定义一个__block变量,如下。可以这么看,Swift中的变量默认都是__block的
//OC
typedef int(^BLOCK)(void);
BLOCK OCFunc (int step) {
__block int total = 0;
__block int step2 = step;
BLOCK b = ^() { total +=step2; return total; };
step2 = 100;
NSLog(@"before +100,%d",b()); //100
total +=100;
NSLog(@"after +100,%d",b()); //300
return b;
}
//在main方法里面调用
BLOCK b = OCFunc(1);
NSLog(@"%d",b()); //400
NSLog(@"%d",b()); //500
这个值捕获和OC的block一样,也会产生循环引用问题。OC里面是使用__weak来解决,这里差不多,它可以在参数列表前面加上捕获列表,并且对捕获类别的参数进行权限控制,附上一个官方例子,以后写ARC的时候详细讲。
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
闭包是引用传递,意味着将一个闭包赋值给另外一个闭包变量的时候,二者是指向同一个闭包。