• 闭包学习记录-iOS开发


    写在前面:在一开始JS基础学习的过程中,闭包的问题也曾困扰过我,其实无论是JS、OC、还是Swift,闭包的本质都是一样的。借着最近开始学习Swift,又重新将这部分复盘整理了一下。目前对于闭包相关的理解可以说是很透彻了,发现新问题再来补充。

    专业的解释:

    闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

    形式:

    Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)相似。

    故名思义,闭合地包起来可以完成一件事儿就是个闭包。闭包这玩意儿,只是给它取了个名字而已,全局函数其实也是个闭包。还有闭包就是自包含的函数代码块,可以在代码中被传递和使用。比如,嵌套函数,函数作为参数的情况。

    全局函数

    嵌套函数

    闭包表达式(轻量级语法)

    有名字

    有名字

    匿名

    不会捕获任何值

    捕获其外部函数所有的参数和定义的常量和变量

    捕获上下文中变量或常量的值

    作用:

    用来解决什么问题呢?闭包解决了函数内变量暴露给函数外访问的问题。可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚。

    值捕获,闭包可以在上下文中捕获常量或变量的引用。即使常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。👇这种情况就是值捕获

    1. // 一般情况
    2. // 闭包情况
    3. func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    4. var runningTotal = 0
    5. func incrementer() -> Int {
    6. runningTotal += amount
    7. return runningTotal
    8. }
    9. return incrementer
    10. }
    11. let incrementByTen = makeIncrementer(forIncrement: 10)
    12. incrementByTen()
    13. // 返回的值为10
    14. incrementByTen()
    15. // 返回的值为20
    16. incrementByTen()
    17. // 返回的值为30
    18. let incrementBySeven = makeIncrementer(forIncrement: 7)
    19. incrementBySeven()
    20. // 返回的值为7
    21. incrementByTen()
    22. // 返回的值为40
    23. incrementBySeven()
    24. // 返回的值为17

    接下来,我们来对👆🌰进行值捕获的解释👇

    1. func incrementer() -> Int {
    2. runningTotal += amount
    3. return runningTotal
    4. }

    「👆incrementer() 函数并没有任何参数,但是在函数体内访问了 runningTotalamount 变量。这是因为它从外围函数捕获了 runningTotalamount 变量的引用。捕获引用保证了 runningTotalamount 变量在调用完 makeIncrementer 后不会消失,并且在下一次执行 incrementer 函数时,runningTotal 依旧存在。」

    形式补充:

    函数和闭包都是引用类型,将函数或闭包赋值给一个常量还是变量,实际上是将函数或闭包的引用设置为常量或变量的值,而并非闭包内容本身。意味着如果将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包。继续👆例子🌰

    1. let alsoIncrementByTen = incrementByTen
    2. alsoIncrementByTen()
    3. // 返回的值为50

    另外关于⚠️闭包引起的循环强引用问题(遇到了再说吧)

    语法:

    函数和返回值类型都写在大括号内

    闭包的函数体部分由关键字 in 引入

    1. { (parameters) -> type in
    2.     statements
    3. }
    4. func backward(_ s1: String, _ s2: String) -> Bool {
    5.     return s1 > s2
    6. }
    7. { (s1: String, s2: String) -> Bool in
    8.     return s1 > s2
    9. }

    语法优化:

    1.省略参数和返回值类型,可根据上下文推断出

    { s1, s2 in return s1 > s2 }

    2.行表达式闭包可以通过省略 return 关键字

    { s1, s2 in s1 > s2 }

    3.参数名称缩写,$0 和 $1 表示闭包中第一个和第二个参数

    { $0 > $1 }

    4.运算符方法【❓这个暂时先保留,知识结构补充ing】

    与 sorted(by:) 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现:reversedNames = names.sorted(by: >)

    5.尾随闭包:

    将闭包表达式写在函数圆括号之后,作为函数的最后一个参数调用。用于闭包长度非常长的情况

    reversedNames = names.sorted() { $0 > $1 }

    若闭包表达式是函数的唯一参数,可以把 () 省略掉

    reversedNames = names.sorted { $0 > $1 }

    应用场景:

    1.逃逸闭包:当一个闭包作为一个函数的参数,且闭包在函数返回之后才被执行。定义函数时,在参数名之前标注 @escaping指明这个闭包允许“逃逸”出这个函数。

    🌰不想复制粘贴,具体例子移步官方文档 逃逸闭包 - SwiftGG

    相信看完文档中的🌰对这个概念已经有了大概的理解,我来浅浅总结一下。很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回但不被调用,直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。

    补充:逃逸闭包需要显示引用self

    项目中的🌰:实现 确认是否安装外部鱼眼镜头的 Alert弹窗

    1. //  这里isUpdateAvailable是VersionUtils的一个属性
    2. func isUpdateAvailable(callback: @escaping (Bool) -> Void) {
    3. callback(true)
    4. return
    5. }
    6. isUpdateAvailable(callback: { isNew in
    7. if isNew {
    8. let alertVC = UpdateVersionAlertVC.shared
    9. alertVC.modalPresentationStyle = .overCurrentContext
    10. alertVC.modalTransitionStyle = .crossDissolve
    11. self.present(UpdateVersionAlertVC.shared, animated: true)
    12. }
    13. }
    14. //这里 把completed赋值给了实例 alertVC的completed属性,在alertVC的一个方法中对alertVC的completed进行传参调用,此时就需要标注@escaping(闭包需要在函数返回之后被调用)
    15. extension FishShotAlertVC {
    16.     @objc static func showFishShotAlertViewController(currentVC vc: UIViewController, completed: @escaping (_ isUseFishShot: Bool) -> Void) {
    17.         let alertVC = FishShotAlertVC()
    18.         alertVC.completed = completed
    19.         alertVC.currentVc = vc
    20.         vc.present(alertVC, animated: false, completion: {})
    21.     }
    22. }

    补充:

    当闭包作为函数的参数,该闭包也有参数时

    1.声明函数时,不考虑闭包的具体内容,{ }里面是函数体,函数体内有对闭包进行传参调用。

    2.函数调用时,作为尾随闭包,{ }里面是尾随闭包

    写在最后:

    学习闭包经历了三个阶段,文档看懂,写的时候语法忘了,

    总而言之就是,以为懂了实际没懂,遇到实际场景返回来继续复盘,和别人讨论,直到可以回答别人提出的任何相关问题,不能解释就继续去学习。这个概念我应该算是彻彻底底懂了吧。

  • 相关阅读:
    ES6-let-难点
    Web前端大作业—里约热内卢奥运会(html+css+javascript)
    vSphere 7.0 主机卸载NFS存储失败故障处理
    财务RPA与数字化转型——财务RPA如何促进企业的数字化转型
    openGauss 社区 2022 年 8 月运作报告
    RK主机 CPU、NPU使用频率查看和设置
    图解Redis 06 | Hash数据类型的原理及应用场景
    猿创征文|Spring MVC学习大总结
    vue-cropper在ie11下选择本地图片后,无显示、拒绝访问的问题
    微信小程序商场项目(SpringBoot)--- 小程序模块
  • 原文地址:https://blog.csdn.net/qq_51315315/article/details/126302268