您的位置:首页 > 移动开发 > Swift

Swift学习笔记7——闭包(Closures)

2015-10-02 15:15 447 查看
其实这个闭包可以看做是匿名的函数。

我们先来回想一下函数作为参数的情况

//定义一个函数,它最后的参数是一个函数类型
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
}


闭包是引用传递,意味着将一个闭包赋值给另外一个闭包变量的时候,二者是指向同一个闭包。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: