在OC开发中,我们经常用block,block原理我估计应该和swift的闭包差不多,现在一起看看swift中的闭包把。
闭包的表达式
{
(parameterTypes) -> (returnType) in
statements 需要执行的代码
}
长话短说,看几种闭包的写法,体验下
let addClosure = {
(num1: Int,num2: Int) -> Int in
return num1 + num2
}
let result = addClosure(10, 20)
是不是清晰了许多,闭包和block,就是保存一段代码
闭包表达式简写
闭包作为函数函数的参数情况,看看下面几个例子,你就明白了
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
let sum = fn(v1,v2)
print(sum)
}
- 最原始的调用写法
exec(v1: 5, v2: 20, fn: {
(v1: Int, v2: Int) -> Int in
return v1 + v2
})
- 去掉闭包的返回值写法
exec(v1: 5, v2: 20, fn: {
(v1: Int, v2: Int) in
return v1 + v2
})
- 在第二步基础上再去掉形参
exec(v1: 5, v2: 10, fn: {
(v1, v2) in
return v1 + v2
})
- 在第三步基础上去去掉
return
exec(v1: 5, v2: 10, fn: {
(v1, v2) in v1 + v2
})
- 闭包的语法糖调用,用1,$2 ...代表闭包的实际参数
exec(v1: 5, v2: 10, fn: {$0 + $1})
- 如果你觉得上面的调用够精简了,那你就太容易满足了
exec(v1: 5, v2: 10, fn: -)
闭包取别名
取别名的意思很好理解,比如我叫宋鸿康
,在公司一般都叫我宋工
,在公司叫我宋工
,我就知道叫的是我,取别名的好处,具体看看几个例子,你看是否有简化
回顾下以前用OC给block取别名
typedef void(^blockName)(void);
在swift中取别名
typealias Fn = (Int, Int) -> (Int)
上面例子的swift
参数用Fn
代替,看代码
typealias Fn = (Int, Int) -> (Int)
func exec(v1: Int, v2: Int, fn: Fn) {
let sum = fn(v1,v2)
print(sum)
}
exec(v1: 5, v2: 10, fn: -)
这就是取别名的用法,和oc中的block取别名用法一样,没啥好说的
尾随闭包
尾随闭包是指闭包表达式作为函数最后一个实际参数传递给函数时,我们叫这个闭包尾随闭包,概念有点苦涩,看看例子
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
let sum = fn(v1,v2)
print(sum)
}
fn 闭包作为函数exec
最后一个参数。fn就是尾随闭包,尾随闭包在使用的时候可以精简
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
let sum = fn(v1,v2)
print(sum)
}
exec(v1: 10, v2: 20) { $0 + $1 }
还有一些写法,参看上面--- 具体就不写了
闭包的循环引用
下面看看在项目中造成循环引用的代码吧、为什么造成了循环引用和block的造成循环引用一样。
block捕获了self,self持有block,具体为什么强引用捕获self,这是一个读者自己去想的问题,这里不做过多解释,block很复杂,一两句也说不说完
上面说的block 就是 闭包
class ViewController: UIViewController {
var closure:(() -> ())?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
closure = {
self.view.backgroundColor = UIColor.red
}
}
deinit {
print("ViewController dealloc")
}
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
closure!()
}
}
上面的代码就造成循环引用
- 解决循环
weak var weakSelf = self
closure = {
weakSelf?.view.backgroundColor = UIColor.red
}
和OC中block,解决引用的方法一样
__weak typeof(self) weakSelf = self; //这里的typeof()只是对括号内的变量进行识别,返回值是变量的类型 = self;
- swift特有解决循环引用的方式
closure = { [weak self]
in self?.view.backgroundColor = UIColor.red
}
3.是用关键字unowned。
closure = { [unowned self]
in self.view.backgroundColor = UIColor.red
}
推荐使用第二种方式解决闭包循环引用
逃逸闭包
1.一个被保存在某个地方等待稍后再调用的闭包就叫做逃逸闭包,通俗点说,闭包在有可能在函数结束后调用,闭包调用逃离了函数的作用域,我们叫这种闭包为逃逸闭包,注意逃逸闭包需要通@escaping
关键字声明
举几个demo
typealias Fn = () -> ()
var fn: Fn?
class Peson {
func test(lsh: @escaping Fn) {
fn = lsh
}
}
var p = Peson()
p.test {
print(111)
}
fn?()
上面的demo闭包lsh被全局变量保存了,等test函数调用完后,在调用闭包,这个比较就是逃逸闭包。
在看一个例子
typealias Fn = () -> ()
class Peson {
func test(lsh: @escaping Fn) {
DispatchQueue.global().async {
lsh()
}
}
}
var p = Peson()
p.test {
print(11)
}
闭包放到异步并发队列中去,也是要等待test函数执行完后,才会执行lsh闭包,这也是逃逸闭包
- 逃逸闭包的注意点
自动闭包
某个参数用闭包表达式装起来并用@autoclosure
声明,就叫自动闭包
自动闭包的作用:能让代码变得更加漂亮
举个自动闭包的例子
func log(ifFalse condition: Bool,
message: @autoclosure () -> (String),
file: String = #file, function: String = #function, line: Int = #line)
{
guard !condition else { return }
print("Assertion failed: \(message()), \(file):\(function) (line \(line))")
}
log(ifFalse: false, message: "111")
自动闭包的注意点
1.只支持() -> T 格式的闭包
2.有@autoclosure、无autoclosure构成了函数重载