swift 闭包详解加应用
1.表达式
闭包表达式包含参数,返回值,及函数体代码(参数不能有默认值)
{
(参数列表) -> 返回值类型 in
函数体代码
}
// 例
var fun = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
// 闭包表达式调用的时候 不用写参数名
fun(10, 20)
//上述代码可直接写成(小括号意思是执行闭包)
{
(v1: Int, v2: Int) -> Int in
return v1 + v2
}(10, 20)
2.省略&简写
闭包可以省略一些参数(开发中应适当省略,不能全部最简,否则可读性太差,不好维护)
- swift可以推断参数类型和返回值类型,因此可以不写类型。
- 单表达式可以不用写return.
- swift中可以简写实际参数名字,可以用
$0,$1
表示。 - 如果闭包表达式是函数的唯一一个参数,小括号也可以省略了。
详细:
可以在闭包中定义参数,返回值。 闭包后用括号执行,并在括号中可以传参。 例如:
var str={ (arg1:String,arg2:String) -> String in
return arg1+arg2
}("str1","str2")
基于上面最全的定义方式, 我么可以省略参数的类型:
var str={ arg1,arg2 -> String in
return arg1+arg2
}("str1","str2")
为什么能省略参数类型?那是因为,swift的类型推导,根据后面括号的传参能自动判断参数的类型。
然后我们可以省略闭包中的返回值类型
var str:String={ arg1,arg2 in
return arg1+arg2
}("str1","str2")
注意,闭包省略了返回值类型后,变量要显示声明它的类型, 之所以能省略返回值类型,那也是因为swift类型推导,先知道了变量的类型,所以可以省略返回值类型。
还不够爽,我们可以把参数也省略了
var str:String={
return $0+$1
}("str1","str2")
如果闭包中只有一行代码, 其实return 也能省略。
var str:String={
$0+$1
}("str1","str2")
如果闭包没有定义参数 ,像这样
var str:String={
return "str1str2"
}()
括号中根本没有传参数, 括号能不能省略呢?
可以把括号省略了
省略括号的同时等号也不能写
var str:String{
return "str1str2"
}
3.尾随闭包
- 尾随闭包:如果函数的最后一个参数是闭包,则闭包可以写在形参小括号的外面。为了增强数的可读性。
stringAdd(str1: "str1", str2: "str2") { str1, str2 in
str1 + str2
}
尾随闭包形式:
stringAdd(str1: "str1", str2: "str2") {
$0 + $1
}
4.值捕获
- 一个函数和它捕获的变量常量环境组合起来称为闭包
- 一般情况下这个函数是定义在函数内部的,也就是这个内部的函数与这个包裹其的函数内部定义的变量一起称之为闭包。
- 闭包一般捕获外层函数的局部变量、常量。
如下面的例子plus以及其捕获的变量a一起才是闭包。
下面我们先看一下代码,可以看到这里内部的函数捕获变量a:
typealias Fn = (Int) -> (Int) // 函数类型
var a = 10
// 这个内嵌的函数其实就是一个闭包,只不过这个闭包已func的形式表现,而不是闭包表达式的形式
func exec() -> Fn {
func plus (_ i: Int) -> Int {
a += i
return a
}
return plus // 其实返回的就是plus地址
}
let fn = exec()
print(fn(1)) //打印结果11
print(fn(2)) //打印结果13
因为这里的a是一个全局变量(a是个全局变量,a的存放地址就在全局数据区,就不用把a放到堆空间,不需要捕获。所以这里没有发生值捕获),因此我们调用了两次访问的都是同一个变量地址,因此这里的结果肯定是连续相加的;
那如果a是一个局部变量呢?
func exec() -> Fn {
var a = 10
func plus (_ i: Int) -> Int {
a += i
return a
}
return plus
}
let fn = exec()
print(fn(1)) //打印结果11
print(fn(2)) //打印结果13
let fn1 = exec() // 此句之后,会给a再次创建一个堆空间
print(fn1(1)) //打印结果11
/*
1>.执行完上面一行代码之后,即调用exec方法之后。
2>.按理说,exec方法执行完,方法里面的局部变量a应该从栈空间被释放.
3>.但是此时fn里面存储了就是plus这个函数地址,
而此时plus方法中用到的这个a会被放到堆内存(值捕获),从而延长了a的生命周期
这样a这个内存就不会在执行完exec之后被销毁。
*/
上述的exec方法里面的闭包可以以闭包表达式的形式简写为:
func exec() -> Fn {
var a = 0
return {
a += $0
return a
}
}
类比总结:可以把闭包想象成一个类的实例对象
- (闭包的)内存在堆空间上。
- 捕获的局部变量、常量就相当于对象的成员(存储属性),当然是在堆内存上。
- 组成闭包的函数就是相当于类内部定义的方法(就是上面的plus方法)。
5.非逃逸闭包、逃逸闭包 escaping
- 非逃逸闭包、逃逸闭包是当闭包作为一个实际参数传递给一个函数。
- 非逃逸闭包:闭包调用发生在函数结束之前,闭包调用在函数作用域内。
- 逃逸闭包:闭包有可能在函数结束后调用,调用逃离了函数的作用域。需要用escaping来修饰闭包是允许逃逸的。多用于网络请求。
注意:让闭包逃逸意味着必须在闭包中显式引用self.
// 非逃逸闭包
func test(_ fn: () -> ()) {
// 在函数作用域里面调用了这个闭包
fn()
}
test {
print(1)
}
// 逃逸闭包
import Dispatch
typealias Fn = () -> ()
var gFn: Fn?
func test2(_ fn: @escaping Fn) {
// 在函数作用域里面没有调用fn这个闭包,而是赋值给了一个变量
gFn = fn
}
func test(_ fn: @escaping Fn) {
DispatchQueue.global().async {
fn()
}
}
6.自动闭包
- 自动闭包是就是将一个表达式自动转成闭包。
- autoclosure只支持 () -> T 格式的参数。
- autoclosure并非只支持最后一个参数。
- 注意:使用了autoclosure的地方,这个值会被延迟执行。
func assert(_ condition: @autoclosure ()->Bool,
_ message: @autoclosure ()-> String) {
return condition() ? message() : "no String"
}
// 调用
assert(number > 3,"number 不大于3")
// autoclosure 会自动将 number > 3 封装成{ number > 3 }
// 如果没有autoclosure修饰,则需要用闭包表达式的方式调用。
assert( { number > 3 }, { "number 不大于3" } )
逃逸闭包和自动闭包的使用
var names = ["Lewis","Jonathan","Zack","Lux"]
var providers: [() -> String] = [] //存放闭包的数组
func collectCustomerProviders(provider: @autoclosure @escaping () -> String) {
providers.append(provider) // 此时闭包没有调用,需要等函数执行完调用所以要用@escaping 修饰
}
// 传入的值为一个String形式,由于用autoclosure修饰,会自动转为闭包类型
collectCustomerProviders(provider: names.remove(at: 0)) //此时names.count = 4
collectCustomerProviders(provider: names.remove(at: 0)) //此时names.count = 4
for provider in providers {
print(provider()) //此时的闭包才被执行
}
// 此时names.count = 2
7.实际应用:
1,cell中赋值model(注:didSet情况,必须有默认值,可以用可选类型或添加默认值,否则报错,set无需设置默认值)
private var model: YourModel? {
didSet {
//具体的赋值操作函数
}
}
2,懒加载创建控件
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = .white
//可以对其他属性进行设置
return scrollView
}()
lazy var tableview: UITableView = {
let tableview = UITableView()
//设置代理或其他操作:注册cell等
tableview.delegate = self
tableview.dataSource = self
return tableview
}()
3,页面传值
下边的用法试用自定制view或cell涉及到的点击事件,以及页面间传值
注:参数类型为任意:Any,传值用swift特有的元组,当然,接收的地方也要用元组类型来接收
var clickBlock = ((Any) -> ())?
func clickAction() {
clickBlocK?((yourValur1, yourValue2))
}
4,网络请求回调
let getDataUrl = "拼接的url"
func getData(parameters: [String: Any], success: @escaping(_ response: [String: Any])->(), failure: @escaping(_ error: Error)->()) {
let urlString = BaseUrl + getDataUrl
AlamofireRequest.getListRequest(urlString: urlString, parameters: parameters, success: { (response) in
success(response)
}) { (error) in
failure(error)
}
}