*****阅读完此文,大概需要5分钟******
一、闭包的基本概念与写法
1、Swift中闭包需要对比OC的block
- OC的基本定义与写法:
返回值(^闭包名称)(参数类型 参数名) = ^(参数类型 参数名){函数体};
void(^XXBlock)(int a) = ^(int a){
NSLog(@"hello");
};
- Swift基本定义与写法:
{(参数:参数类型)->返回值类型 in 函数体}如果设置了返回值,记得返回对应类型的值
let test = {(a:Int)->Int in
return a;
};
个人感觉Swift的闭包写法较为精简并且好记。如果手写,相信很多人应该深有感触,面试时手写block简直是噩梦。相比较而言,Swift的闭包更容易手写出来。
2、Swift的闭包实例应用
- 闭包作为属性的实例应用:
// KXRequest.swift
typealias RequestBlock = (_ name:String)->Void;
var block : RequestBlock?;
func show() {
self.block?("kx")
}
// ViewController.swift
let request = KXRequest();
request.block = { (name:String) in
print(name)
};
request.show()
- 闭包作为函数参数
func requestInfo(complection:((Bool, Array<String>) -> Void)? = nil) {
complection?(false, [])
}
self.requestInfo { [weal self] (sucess, result) in
guard let self = self else {return}
If !sucess {
return
}
print(result)
}
二、后置闭包、自动闭包、逃逸闭包等
1、后置闭包
又称尾随闭包,是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted() { $0 > $1 }
print(reversed)
- 单个后置闭包的应用1:
//单个闭包
UIView.animate(withDuration: 0.3, animations: {() in
})
- 单个后置闭包的应用2:
class MessageContentModel: AnyObject {
var timestamp: TimeInterval = 0
var identifier: String = "MessageModel"
…
}
let sortedMessageModels = messageModels.sorted(by: {
$0.timestamp < $1.timestamp
})
- 多个后置闭包应用1:
//多个闭包
UIView.animate(withDuration: 0.3. animations: { () in
// …
}, completion: { (finish) in
// ….
})
- 多个后置闭包应用2
let request = ABRequest(info)
request.startWithCompletionBlock { [weak self] (req) in
guard let self = self, let request = req as? ABRequest else {return}
self.handleResponce(req: request, userId: info.userId)
} failure: { [weak self] (req) in
guard let self = self, let request = req as? ABRequest else {return}
self.handleResponce(req: request, userId: info.userId)
}
//或者
let request = ABRequest(info)
request.startWithCompletionBlock(success: { [weak self] (req) in
guard let self = self, let request = req as? ABRequest else {return}
self.handleResponce(req: request, userId: info.userId)
}) { [weak self] (req) in
guard let self = self, let request = req as? ABRequest else {return}
self.handleResponce(req: request, userId: info.userId)
}
2、自动闭包@autoclosure
autoclosure顾名思义,这是一种可以“自动”的Closure,即可以让表达式的类型转为相应的Closure的类型,在原本的调用处,可以省略Closure参数的大括号。其只可以修饰作为参数的 Closure,但该 Closure 必须为无参,返回值可有可无。
func kClosureExample(_ compression: () -> Bool) {
if compression() {
print("true")
}
}
kClosureExample { 1 < 2 }
func kAutoClosureExample (_ compression: @autoclosure () -> Bool) {
if compression() {
print("true")
}
}
kAutoClosureExample( 1 < 2 )
3、逃逸闭包与非逃逸闭包
Swift 3.0之后,传递闭包到函数中的时候,系统会默认为非逃逸闭包类型@noescaping,逃逸闭包在闭包前要添加@escaping关键字。它们是根据闭包的调用时的时机划分的。
- 非逃逸闭包
不需要其它特殊时机,只能在函数作用域内函数执行结束前被调用。非逃逸闭包的生命周期与函数相同:
A. 把闭包作为参数传给函数,
B. 函数中调用闭包,
C. 退出函数,结束。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.myTest {
print("非逃逸(noescaping)")
}
}
func myTest(callback:()->()) {
print("before")
callback()
print("after")
}
}
- 逃逸闭包
不需要在函数结束前被调用,可以等到特定时机时才被调用。逃逸闭包的生命周期:
A. 闭包作为参数传递给函数,
B. 退出函数,
C. 闭包被调用,闭包生命周期结束
class ViewController: UIViewController {
var escapingCallback : (()->())?
override func viewDidLoad() {
super.viewDidLoad()
self.myTest {
print("逃逸(escaping)")
}
escapingCallback?()
}
func myTest(callback:@escaping ()->()) {
escapingCallback = callback
}
}
给当前类声明一个闭包变量escapingCallback,将myTest函数内的闭包参数赋值给这个变量,此时这个闭包就可以在函数执行结束后被调用。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.myTest{
print("逃逸(escaping)")
}
}
func myTest(callback:@escaping()->()) {
print("before”)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
callback()
}
print("after")
}
}
不需要在myTest函数结束前调用,等到1秒后再调用闭包callback。
三、闭包中的循环引用与解决方法
类似OC的Block,循环引用的方式有三种:
- 使用weak修饰变量, 打破强引用,实例代码:
weak var weakSelf = self
loadData { (textString) -> () in
print("\(textString) \(weakSelf?.view)")
}
- 使用[weak self] 修饰闭包原理跟__weak类似, 这样在闭包中使用self, 就是弱引用
// [weak self]
loadData { [weak self] (textString) -> () in
//闭包中的self 都是弱引用的
print("\(textString) \(self?.view)")
}
- 使用[unowned self ] 修饰闭包, 跟__unsafe_unretained类似, 慎用
loadData { [unowned self] (dataString) -> () in
print("\(dataString) \(self.view)")
}
- [weak self] 与__weak,[unowned self ]与__unsafe_unretained
__weak 表示的是对象的弱引用关系,__weak修饰的对象被释放后,指向对象的指针会自动置为空,也就是指向nil。__unsafe_unretained表示的是弱引用关系,__unsafe_unretained修饰的对象被释放后,指针不会置为空,变成一个野指针,如果后续再访问这个对象可能就会crash。所以[unowned self ]与__unsafe_unretained都要慎用。[unowned self ]与__unsafe_unretained解除循环引用,不被释放, 一般必须是自己的实例属性。
四、与OC的block的不同
上面可以看出OC的block与Swift的闭包基本一致,但是它们也有区别的地方。
- Swift的闭包和OC的Block里值的捕获的区别
OC代码捕获实例:
NSInteger a = 100;
void(^block)(void) = ^{
NSLog(@"block = %ld:", a);
};
a += 1;
NSLog(@"out1 = %ld:", a);
block();
NSLog(@"out2 = %ld:", a);
结果打印如下:
2021-08-17 11:27:13.846743+0800 MDProject[30746:23593763] out1 = 101
2021-08-17 11:27:13.846885+0800 MDProject[30746:23593763] block = 100
2021-08-17 11:27:13.847002+0800 MDProject[30746:23593763] out2 = 101
在OC编译器走到第三行的时候,实际上已经完成了对a的拷贝。
Swift代码捕获实例:
var a = 100
let closure = {
print("closure = \(a)")
}
a += 1
print("out 1 = \(a)")
closure()
print("out 2 = \(a)")
结果打印如下:
out1 = 101
closure = 101
out 2 = 101
Swift闭包都是捕获的是“引用”,而不是他们引用的对象。
- 闭包中修改值:
var a = 100
let closure = {
a += 1
print("closure = \(a)")
}
a += 1
print("out 1 = \(a)")
closure()
print("out 2 = \(a)")
在闭包里多了一行 a += 1 编译,没有警告,运行结果如下:
out 1 = 101
closure = 102
out 2 = 102
也就是说,在Swift里,闭包就像是oc给外部变量默认添加了__block或者__weak
- Swift里的捕获列表(capturing list)
Swift里实现和OC一样的值捕获,看如下代码:
var a = 100
let closure = {
[a] in
print("closure = \(a)")
}
a += 1
print("out 1 = \(a)")
closure()
print("out 2 = \(a)")
闭包内部多了一行[a] in 语法为中括号[]里面添加捕获的变量,然后用in 分割上下分。结果打印如下:
out 1 = 101
closure = 100
out 2 = 101
五、参考文档
以上部分片段摘录自以下文档:
https://www.jianshu.com/p/33707bd27500
https://www.jianshu.com/p/61ee76234357
https://blog.csdn.net/qq_30932479/article/details/80517518
https://www.jianshu.com/p/cbd0b7390bcf
https://www.jianshu.com/p/65d0d6a1ced1
https://www.dazhuanlan.com/abiosis/topics/1242173
https://www.runoob.com/swift/swift-closures.html
感谢作者们的分享。