swift闭包运用

闭包是swift中相当重要的一个概念,他不仅为swift开发自定义调用执行逻辑提供了方便。同时也是Swift中函数式编程之所以强大的一个重要原因。
这里我们就来探索一下swift闭包的一些用法。
首先从适用场景来讲,闭包基本上可以分为非逃逸闭包和逃逸闭包两种。这个概念在其他语言中是没有的。而swift中引出这个概念,从而让我们能够更好的理解闭包的生命周期。

非逃逸闭包

swift默认的闭包即为非逃逸闭包。其生命周期仅限于闭包参数所在函数的生命周期内有效。一旦超越此作用于,将会导致编译异常。主要体现在两个方面:

  1. 将非逃逸闭包作为参数赋值给对象的一个属性时,会报语法错误提示:
Assigning non-escaping parameter 'xxx' to an @escaping closure
  1. 将非逃逸闭包放在异步执行逻辑中使用则会出现如下语法错误:
Escaping closure captures non-escaping parameter 'xxx'

之所以存在这样的问题,就在与非逃逸闭包的作用域仅限于当前函数本身的生命周期,一旦作为属性或者异步逻辑中的调用存在,将扩大闭包本身的作用于,这与非逃逸闭包的作用于相违背。
例如如下用法即为错误:

func minMax(limit handle: (Int) -> Int) {
        DispatchQueue.main.async {
            _ = handle(1)
        }
}

而正确的用法则为

func minMax(limit handle: (Int) -> Int) -> Int {
        return = handle(1)
}

由此,也就引出了另一个需要讨论的话题逃逸闭包。

逃逸闭包

逃逸闭包可以干啥?简单来说就是非逃逸闭包不能干的两件事,逃逸闭包都能干。此外,非逃逸闭包能做的事情,逃逸闭包当然也能干了。
也就是对于逃逸闭包,可以做如下定义:

@propertyWrapper
class AgeWrapper {
    var wrappedValue: Int {
        get {
            return age
        }
        set {
            age = limitHandle(newValue)
        }
    }
    var age: Int = 0
    var limitHandle: (Int) -> Int
    init(limit handle: @escaping (Int) -> Int) {
        self.limitHandle = handle
        print(handle(199))
    }
}

然后执行如下调用

@AgeWrapper(limit: { max(0, min($0, 121)) }) var age: Int
print("old value -> \(age)")
age = 139
print("new value -> \(age)")

既然如此,是不是说,我们可以直接所有的都用逃逸闭包,而不需要使用非逃逸闭包了呢?当然不是的。试想,如果我们看到的所有暴露的涉及到闭包的函数,看到的都是逃逸闭包,那我们是不是会担心那些原本只作用于当前函数,而在函数结束即结束生命周期的地方,穿入的闭包参数的影响是否会扩大化,从而为编码带来大量的负面效应。因此,在使用闭包的时候,我们应该遵循一个原则:

能用非逃逸闭包就用非逃逸闭包,只有在无法满足这个条件的前提下才用逃逸闭包。

了解了闭包的两种类型之外。我们接下来还要讨论一个概念:自动闭包 @autoclosure。

自动闭包(@autoclosure)

为什么要把自动闭包与非逃逸闭包和逃逸闭包分列出来讲?主要原因在于:

  1. 自动闭包与非逃逸闭包和逃逸闭包在使用上,扩展了概念,使得这类闭包支持表达式方式传参。例如,我们定义了如下函数:
func makeAutoIncrement(closure: @autoclosure () -> Int) -> Int {
        return closure()
    }

在使用上,我们可以使用表达式的形式来传参:

var age = 30
age = makeAutoIncrement(closure: age + 5)
print(age)
age = makeAutoIncrement(closure: age + 3)
print(age)

这使得语法上,更像是传递一个值,但与值传递有所不同的是,自动闭包,会将穿入的数据计算时间后延,从而避免在某些条件下无需执行结果的情况下导致计算耗时。

  1. 自动闭包与非逃逸闭包和逃逸闭包又存在使用上的关联性。这表现在,对于自动闭包,默认其实也是一个非逃逸闭包。而,如果我们在自动闭包修饰符继续使用修饰符@escaping 那么我们将得到一个逃逸的自动闭包。如下:
class WPerson {
        var age: Int
}

func setAutoIncrement(closure: @autoclosure @escaping () -> Int) {
        handle = closure
}

然后进行如下调用

var person = WPerson()
setAutoIncrement(closure: person.age + 3)
person.age = handle()
print(person.age)
person.age = handle()
print(person.age)

我们会发现其打印结果如下:

3
6

变量捕获

在最后一个板块的知识,我们将来了解一下关于闭包中,值的捕获问题。
闭包对与外部变量的捕获,实质上是对变量的一个引用。在内部没有定义同名变量的情况下,闭包中对变量的修改将作用到变量本身。参考示例:

func makeHandle() -> () -> Int {
        var value = 3
        return {
            value = value + 3
            return value
        }
}

我们做如下调用:

let handle = makeHandle()
print(handle())
print(handle())

运行得到如下结果:

6
9

最后,闭包还有许多的语法糖操作,如闭包表达式使用、尾随闭包等,就不在做详细介绍了。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 闭包是可以在你的代码中被传递和引用的功能性独立代码块。 闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,...
    SunshineBrother阅读 513评论 0 4
  • 第九节课:闭包(二) 闭包补充 上节课我们看了捕获一个变量的内存结构,如果捕获的是两个变量的值,当前内存结构是什么...
    不说ryo阅读 442评论 0 1
  • swift进阶 学习大纲[https://www.jianshu.com/p/0fc67b373540] 在 sw...
    markhetao阅读 912评论 0 3
  • 一:函数类型 每个函数都有种特定的函数类型,函数的类型由函数的参数类型和返回类型组成。 上述代码中 (Double...
    Lee_Toto阅读 604评论 0 1
  • 闭包 Swift的闭包类型类似于C和Objective-C中的块以及其他编程语言的Lambdas。 闭包可捕获并存...
    暗夜夜夜行路阅读 398评论 0 1