Swift学习笔记7——闭包(Closures)
2015-10-02 15:15
447 查看
其实这个闭包可以看做是匿名的函数。
我们先来回想一下函数作为参数的情况
如果我们想用doMath实现两个数相减的方法,那么必须再写定义一个sub函数,然后将其作为参数传入。这样在功能多了之后会显得很麻烦,一堆函数,而所以有了闭包这个概念。
闭包的语法
{ (参数列表) -> 返回类型 in
//闭包体
}
有了闭包,我们可以将上面的代码改为
还是很麻烦是吧? 别忘了Swift有类型推断功能,所以我们可以继续简化上面的闭包部分代码
对应只有一行代码的闭包,return关键字还可以省略
此外,闭包对参数提供了默认名字,依次为 $0,$1,$2....所以上面的闭包仍可以简化
对于闭包在参数列表最后一项的情况,可以将闭包写到小括号外部,并且可以省略掉外部参数名
Autoclosures
姑且叫自动打包吧。用大括号括起来就好,编译器自动判断这个大括号里面的是什么返回类型。但是有时候不准确,需要自己写。下面是这个概念的解释,其实也是一种定义闭包变量的方法。
定义了一个Void->Void类型的闭包。因为没有参数,所以可以省略参数列表和in关键字。如果有参数的话,就不能省略in关键字。
因为闭包其实就是函数,调用这个闭包就和调用函数一样。但是有区别的就是闭包都是没有外部外部参数名,调用的时候不要把内部参数名但做外部参数名使用。
有时候函数需要传递一个闭包的时候,可以在调用的时候使用大括号将一段代码生成为闭包。
此外,可以在函数参数列表里面使用@autoclosure关键字,这样就不用使用大括号封装了。但是对于多句的代码情况不行(上面的第二种),有时候自动封装也会出错,比如用上面的第一种情况,它把b()看做了Int,然后报错。需要将返回类型重新定义一下
如果想要自动封装的闭包可以在doClosures函数的作用域以外使用,那么加上escaping关键字。这个关键字只能用在@autoclosure后面。
闭包的值捕获
在生成一个闭包的时候,闭包会将它用到的参数和变量都保存一份。提醒一下,其实闭包就是函数。
上面的函数里面生成了嵌套函数,通过输入不同的符号,返回不同的函数。这里有两个变量需要注意,一个是total,一个是step。当生成嵌套函数的时候,嵌套函数会将这两个变量都copy一份,然后保存起来。下面是对上面代码的一个使用
可以看到,f1和f2的total和step是不会相互干涉的。
再来看看这个值捕获的时间,看下面代码。这里可以看到,值捕获是发生在返回之前。这个和OC的block是一样的。
看到这里,可能大家会以为这个值捕获和OC的block差不多,但是其实差远了。这个值捕获的时间很有区别。这里明显的一点就是我们在函数内部改变外部变量total的时候,没有加任何修饰符,OC里面必须加上__block,要么就是对全局变量进行修改。
我们先看一段OC代码
来看OC和swift中两段很类似的代码
这里可以看到,OC中的step在block定义的时候就绑定了,后面在更改step的值也不影响block。但是在swift中,step仍然是可以改变的,直到step离开作用域后,闭包才将其捕获。
如果要OC中产生同样的效果,只需定义一个__block变量,如下。可以这么看,Swift中的变量默认都是__block的
这个值捕获和OC的block一样,也会产生循环引用问题。OC里面是使用__weak来解决,这里差不多,它可以在参数列表前面加上捕获列表,并且对捕获类别的参数进行权限控制,附上一个官方例子,以后写ARC的时候详细讲。
闭包是引用传递,意味着将一个闭包赋值给另外一个闭包变量的时候,二者是指向同一个闭包。
我们先来回想一下函数作为参数的情况
//定义一个函数,它最后的参数是一个函数类型 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 }
闭包是引用传递,意味着将一个闭包赋值给另外一个闭包变量的时候,二者是指向同一个闭包。
相关文章推荐
- swift元组
- Swift学习笔记6——函数(Function)
- Swift 语言的相关数据
- Swift语法特点
- swift学习
- swift详解之二十九------谈谈debug的一些高级用法lldb
- 我的IOS成长之路——by Swift
- 初步swift语言学习笔记9(OC与Swift杂)
- Swift学习笔记7:关闭
- Swift1_关闭
- swift 注意事项 (十六) —— 可选链
- Swift教程-视频拍摄教程
- What's New in Swift 2 —— guard关键词
- swift练习二
- Swift快速入门-3-運算子、運算元
- Swift快速入门-2-型别安全及推断、整点浮点转换
- swift开发笔记9 - 正向和反向页面传参
- Swift学习- 下标脚本(十二)
- 写Swift用Cocoapods兼容IOS7
- iOS手势操作(Swift)