闭包是swift中非常重要的一个知识点。类似于objective-c中的block,其实函数就相当于一个特殊的闭包。闭包需要提前写好,在适当的时候再执行。
1、定义闭包
闭包的格式是(参数列表)->(返回值类型) in 实现代码
举一个最简单的例子。用常量记录一个代码块,按住option键就能看到,b1是一个闭包。再到适合的地方去调用它。
闭包1.png
let b1 = {
print("干掉他们")
}
b1()
再来看一个带参数的闭包。在闭包中,参数、返回值和实现代码都是写在花括号里面的。in是用来定义分割和实现的。
let b2 = {
(x:String)->() in print(x)
}
b2("string")
2、闭包案例
这个案例要模拟封装一个网络请求的类。利用闭包将jsonData类型的数据传递给展示页面。
-
创建一个新的项目,选择swift语言
闭包2.png - 封装一个网络请求的类HttpTool.swift继承自NSObject
- 用异步线程模拟网络数据请求,再回到主线程中回调闭包
class HttpTool: NSObject {
//闭包类型:(参数列表)->(返回值类型)
func loadData(callback:@escaping(_ jsonData : String)->()) {
DispatchQueue.global().async {
print("发生网络请求:(Thread.current)")
}
DispatchQueue.main.async {
()->Void in
print("获取到数据,并且回调:(Thread.current)")
callback("jsonData数据")
}
}
}
- 到需要接收数据的界面定义Httptool类的属性,设置一个初始化值,将初始值赋值给变量
- 在swift中是不需要引入头文件的,文件之间可共享
import UIKit
class ViewController: UIViewController {
var tools : HttpTool = HttpTool()
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
//用闭包将json数据拿到
tools.loadData { (jsonData) ->() in
print("在viewcontroller中拿到数据(jsonData)" )
}
}
}
闭包3.png
3、尾随闭包
尾随闭包用于需要将一个很长的闭包表达式作为最后一个参数传递给函数。也就是说如果按时的最后一个参数是闭包,那么在调用它的时候就可以把这个闭包写在括号外面,并紧跟括号,函数的其他参数则仍然写在括号之中。
//这个函数接受一个String和一个闭包
//函数体内调用闭包,并且将String作为参数传递给闭包
func myFunc(strP:String,closeP:(String)->Void) {
closeP(strP)
}
//普通调用
myFunc(strP: "hello", closeP: {(string) in print(string)})
//尾随闭包
myFunc(strP: "hello") {
(string) in print(string)
}
4、逃逸闭包
当一个闭包作为参数传到一个函数中,但是该闭包要在函数返回之后才被执行,于是就称这样的闭包为逃逸闭包。也就是说闭包逃离了函数的作用域。写法是在这个闭包参数前加一个@escaping用来指明这个闭包是允许逃逸出该函数的。
-
声明一个方法,这个方法是一个逃逸闭包
-
该方法要做的事情,就是将闭包添加到数组中去
//定义数组,里面的元素都是闭包类型的
var callBackArray : [()->Void] = []
//定义一个接收闭包的函数
func testEscapingClosure(callBack:@escaping ()-> Void) {
callBackArray.append(callBack)
}
- 当改变数组的时候,取第0个元素调用。此时就改变了变量x的值
class SomeClass {
var x = 10
func doSomething(){
testEscapingClosure {
self.x = 100
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
callBackArray.first?()
print(instance.x)
因为逃逸闭包是函数执行之后才会执行,所以可以这样理解:创建一个类的对象instance;在对象中初始化一个x=10;利用对象执行了函数doSomething;函数内部调用全局函数testEscapingClosure,期望修改instance对象的x值为100,但是此时并没有执行这个包含了赋值语句的闭包。
查找全局数组callBackArray,找到里面第一个元素,显然找到的是在testEscapingClosure函数中添加的闭包{self.x = 100},此时才通过全局数组的查询找出闭包并执行,于是x此时才被赋值为100。这就是在函数执行完毕后才执行闭包。刚好符合逃逸闭包的定义。
结论: 逃逸闭包将在函数执行之后执行,于是这段代码最后输出为100是因为闭包最后才被执行……
解决循环引用的三种方式
1、可以使用weak关键字将对象之间的联系变为弱引用
weak var weakself = self
2、第一种方式的简化
[weak self]
3、使用unowned解决
[unowned self]
但是该方法十分危险,要确保数据一定有值。否则会发生奔溃。
__weak 与__unretained有何区别?
__weak修饰的弱引用,如果指向的对象被销毁,那么指针会立马指向nil
__unretained修饰的弱引用,如果指向的对象被销毁,它的指针依然会指向之前的内存地址,很容易产生野指针(僵尸对象)
网友评论