闭包方法的学习

2023-05-16

相关链接

swift中使用@noescape的正确姿势

文档闭包

闭包可以捕获和存储其在上下文中任意常量和变量的引用。被称为包裹常量和变量。Swift会为你管理在捕获过程中涉及到的所有内存操作。

在函数章节中介绍的全局和嵌套函数也是特殊的闭包,闭包采取如下三种形式之一:

  • 全局函数式一个有名字但不会捕获任何值的闭包
  • 嵌套函数是一个有名字并可以捕获其封闭函数域内值得闭包
  • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量和常量值的匿名闭包

Swift的闭包表达式有用简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:

  • 利用上下文推断参数和返回值类型
  • 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
  • 参数名称缩写
  • 尾随闭包语法

闭包表达式

sorted方法
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

let names = ["C","A","E","D","B"]
//        let reversedNames = names.sorted(by: {$0 > $1})
   // 两者相同
let reversedNames = names.sorted(by: backward)
print("\(reversedNames)")
闭包表达式语法
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
   return s1 > s2
})

// 也可以写一行
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
根据上下文推断类型

因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略

let reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })
单表达式闭包隐式返回

单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果

let reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })
参数名称所写

Swift自动为内联闭包提供了参数名称所写功能,你可以直接通过$0$1,$2来顺序调用闭包的参数,依次类推,如果你在闭包表达式中使用参数名称所写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因此此时闭包表达式完全由闭包函数体构成:

let reversedNames = names.sorted(by: { $0 > $1 })
运算符方法
let reversedNames = names.sorted(by: >)
尾随闭包法

如果你需要一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签。

         func someFunctionThatTakesAClosure(closure: (_ str: String) -> String) {
            // 函数体部分
            print("可以判断是谁调取的:\(closure("给你一"))")
         }

         // 以下是不使用尾随闭包进行函数调用
        someFunctionThatTakesAClosure(closure: { str in
            // 闭包主体部分
            print("返回二 \(str)")
            return "返回二"

        })

        // 以下是使用尾随闭包进行函数调用
        someFunctionThatTakesAClosure() { str in

            // 闭包主体部分
            print(str)
            return "返回三"
        }

        // sorted(by:) 也可以使用这样写
        let reversedNames = names.sorted() { $0 > $1 }

如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包的使用,甚至可以把()去掉

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

当闭包非常的长以至于不能在一行中进行书写时,尾随闭包变的非常有用。举例来说,Swift的 Array类型有一个 map(_:)方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。

当提供给数组的闭包应用于每个数组元素后,map(_:) 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值

        let strings = numbers.map { (number) -> String in
            var number = number
            var output = ""

            repeat {
                output = digitNames[number % 10]! + output
                number /= 10
            } while number > 0

            return output
        }
        print(strings)

map(_:) 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数的 number 的类型,因为可以通过要映射的数据类型进行推断

值捕获

注意

如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift使用捕获列表来打破这种循环强引用。


   func makeIncrement(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0

        func incrementer() -> Int {

            runningTotal += amount
            return runningTotal
        }
        return incrementer
    }

如果我们单独考虑嵌套函数 incrementer() 我们会发现它有些不同寻常:

func incrementer() -> Int {
   runningTotal += amount
   return runningTotal
}

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

调用此函数方法

   func makeIncrement(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0

        func incrementer() -> Int {

            runningTotal += amount
            return runningTotal
        }
        print("runningTotal ****** \(runningTotal)")
        return incrementer
    }

    let incrementByTen = makeIncrement(forIncrement: 10)

    print(incrementByTen())
    print(incrementByTen())
    print(incrementByTen())
    print(incrementByTen())

    /*
    10
    20
    30
    40
    */

该例子定义了一个叫做 incrementByTen 的常量,该常量指向一个每次调用会将 runningTotal 变量增加 10incrementer 函数。如果从新创建一个 incrementer, 它会有属于自己的引用,指向一个全新、独立的 `runningTotal变量:

闭包是引用类型

上面的例子中,incrementBySevenincrementByTen 都是常量,但是这些常量指向的闭包仍然可以增加其不活动额变量的值。这是因为函数和闭包都是引用类型。
无论你将函数或闭包赋值给一个常量还是变量,你实际上都是讲常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用 incrementByTen 而并非闭包内容本省。
这也意味着如果你讲闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值为50
逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。

   var completionHandlers: [() -> Void] = []
   func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
      completionHandlers.append(completionHandler)
   }

someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 @escaping,就会得到一个编译错误。
将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 self

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}


class someClass {
    var x = 10
    func doSomthing() {
        someFunctionWithEscapingClosure {
            self.x = 100
            print("调取了1")
        }

        someFunctionWithNonescapingClosure {
            x = 200
            print("调取了2")
        }
    }
}

let instance = someClass()

instance.doSomthing()
print(instance.x)

// 将函数中的闭包方法在函数外调取,相当于block异地调
completionHandlers.first?()
print(instance.x)

// 结果
调取了2
200
调取了1
100
自动闭包

自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种遍历语法让你能够省略闭包的花括号,用一个普通的表达式来代替显示的闭包。
自动闭包让你能够延迟求值,因为知道你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它能控制代码的执行时机。

var customersInLine = ["C","A","E","B","D","F"]
print(customersInLine.count)

// 此处只是一个简单的函数赋值,并不会执行移除
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)

print("现在,移除第一个元素 \(customerProvider())")

print(customersInLine.count)

尽管在闭包的代码中,customersInLin的第一个元素被移除了,不过在闭包在调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,这意味着列表中的元素永远不会被执行。

将闭包作为参数传递给函数时,你能获得同样的延时求值行为。


// 自定义的一个自己需要的符合要求的函数式
func test() -> String {
    return "我很好"
}

// 在此处固定闭包函数类型
func serve(customer customerProvider: () -> String) {
    print("此处调取方法 \(customerProvider())")
}

// autoclosure的使用对比
func serveOne(customer customerProvider: @autoclosure () -> String ) {
    print("调取闭包方法:\(customerProvider())")
}

var customersInLine = ["C","A","E","B","D","F"]
print(customersInLine.count)

// 此处只是一个简单的函数赋值,并不会执行移除
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)

print("现在,移除第一个元素 \(customerProvider())")
print(customersInLine.count)

// 直接在自身处理闭包
serve { () -> String in
    // 这种是自定义
    return "你好啊"
}

// 在外部调取闭包
serve(customer: test)

// 这种是将固定类型的放进去
serve(customer: { customersInLine.remove(at: 0) })
// 加了autoclosure,相当于去掉了外部括号,不太好看出是自动闭包
serveOne(customer: customersInLine.remove(at: 0))

/**结果**/
6
6
现在,移除第一个元素 C
5
此处调取方法 你好啊
此处调取方法 我很好
此处调取方法 A
调取闭包方法:E

通过将参数标记为 @autoclosure 来接收一个自动闭包。现在你可以将该函数当做接受 String 类型参数(而非闭包)的函数来调用。customerProvider 参数将自动转化为一个闭包,因为该参数被标记了 @autoclosure 特性。

// autoclosure
func serveOne(customer customerProvider: @autoclosure () -> String ) {
    print("调取闭包方法:\(customerProvider())")
}

serveOne(customer: customersInLine.remove(at: 0))

注意:

过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。

如果你想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure@escaping 属性。

var customerProviders: [() -> String] = []

func collectCustomerProviders(_ customerProveder: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProveder)
}

var customersInLine = ["C","A","E","B","D","F"]

collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

for customerProvider in customerProviders {

    print("自动闭包:\(customerProvider())")
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

闭包方法的学习 的相关文章

  • 经典编程问题(13)汉诺塔

    背景 汉诺塔 xff0c 又称河内塔 hanoi xff0c 源于印度古老的传说 该传说中要求在三个柱子间移动金质的圆盘 xff0c 规则是 xff1a 每次只能移动一个盘子大盘子不能放在小盘子的上方开始的时候 xff0c 在某根柱子上有
  • 2011年终总结——思路

    By Alpha G wu 又是一年的尽头 xff0c 对于本人来说 xff0c 不是老了一岁 xff0c 而是又长大了一岁 每一天的自己都是一个fresh的自己 xff0c 更别说一年的变化有多大 xff0c 大到甚至连你自己都察觉不出来
  • win10下完美安装ubuntu使用docker容器

    期待以久的windows 10支持ubuntu版本更新终于发布了 xff0c 怀着期待的心情 xff0c 下载更新到2004版本 xff08 设置 gt 关于中查看 xff09 如果版本较低 xff0c 可以使用win10更新包提示 xff
  • 学习ing

    分治算法 一 基本概念 在计算机科学中 xff0c 分治法是一种很重要的算法 字面上的解释是 分而治之 xff0c 就是把一个复杂的问题分成两个或更多的相同或相似的子问题 xff0c 再把子问题分成更小的子问题 直到最后子问题可以简单的直接
  • C语言中字符串相关操作函数

    长度 strlen size t strlen const char str 功能 xff1a 返回字符串长度 strnlen size t strnlen const char str size t maxlen 功能 xff1a 返回字
  • s3c2440之串口

    1 xff1a s3c2440串口概述 s3c2440A 通用异步接收器和发送器 xff08 UART xff09 提供了三个独立的异步串行I O xff08 SIO xff09 端 口 xff0c 每个端口都可以在中断模式或DMA模式下操
  • 《layui宇宙版教程》:日期和时间组件laydate

    Layui宇宙版教程 提供2000人的QQ群进行交流学习 xff0c QQ群号 xff1a 1046961650 xff0c 或通过手机QQ扫描二维码进入 xff1a 1 19 日期和时间组件laydate 主要以年选择器 年月选择器 日期
  • 关于/etc/init.d/rcS not found的解决方法

    进入rescue模式 xff0c 选择root 输入命令 lt pre name 61 34 code 34 class 61 34 cpp 34 gt mount o remount rw touch etc init d rc vim
  • 飞马D200S无人机与机载激光雷达在大比例尺DEM建设中的应用

    摘要 本文基于飞马D200S无人机搭载RIEGLmini210激光雷达获取46 7km2的点云数据制作1 500DEM 首先根据测区面积大 xff0c 飞马无人机管家智航线模块自动划分航摄分区过多的情况 xff0c 调整航线长度以减少航摄分
  • 2023RSAC创新沙盒大赛十强公布,软件供应链与开源软件安全再成焦点

    近日 xff0c 2023RSAC大会公布了第18届RSAC创新沙盒竞赛的决赛 十强企业 软件安全企业Endor Labs成功入围 xff0c 软件供应链安全和开源安全问题再次成为国际焦点 Endor Labs提供了一个可依赖的生命周期管理
  • 如何找到现有研发体系的「内耗问题」?| 谈效风生

    第3期 xff1a 如何找到现有研发体系的 内耗问题 xff1f 在上一期 谈到提升效能 xff0c 我们应该如何下手 xff1f 我们聊到开始做研发效能的四个要点 xff1a 评估现有流程 引入自动化工具 建立度量指标 持续改进 本期就围
  • Ubuntu 18之vnc连接不上问题(已解决)

    在配置vnc时所以的准备动作已经准备好了 xff0c 该配的文件也配好了 xff0c 但就是一直连接不上 在主机端报time out的错误 xff0c 后来查百度得知vncserver xff1a 1对应5901端口 xff0c 2就是59
  • 「自动化」聊起来简单,做起来难 | 谈效风生

    第4期 xff1a 自动化 聊起来简单 xff0c 做起来难 在上一期 如何找到现有研发体系的 内耗问题 中 xff0c 我们聊了评估现有研发体系 xff0c 正确的找到 体系内耗问题 xff0c 是改变研发体系的第一步 本期我们继续聊下一
  • GitChat·前端 | 从软件工程角度看大前端技术栈

    GitChat 作者 xff1a 韩亦乐 前言 我们都知道 xff0c 大学几乎是没有 Web 前端课的 以我所在的大学为例 xff0c 唯一引导我们了解 JavaScript 的也只是 人机界面 和 Web应用开发 选修课 再者 xff0
  • 当我说要做大数据工程师时他们都笑我,直到三个月后……

    作者 xff1a Fickr孫啟誠 原文 xff1a 三个月大数据研发学习计划实战解析 关注微信公众号 xff1a GitChat 技术杂谈 一本正经的讲技术 不要错过文末彩蛋 申明 xff1a 本文旨在为普通程序员 xff08 Java程
  • 原来这样做,才能向架构师靠近

    说在前面 xff1a 达人课是GitChat的一款轻阅读产品 xff0c 由特约讲师独家发布 每一个课程你都可获得6 12篇的深度文章 xff0c 同时可在读者圈与讲师互动交流 GitChat达人课 xff0c 让技术分享更简单 进入我的G
  • 带你从零开始,快速学会 Matlab GUI

    本文来自作者 木木小迷哥 在 GitChat 上分享 Matlab GUI 零基础学员快速入门 xff0c 阅读原文 查看交流实录 文末高能 编辑 洛肯 Matlab广泛地应用于数学分析 计算 自动控制 系统仿真 数字信号处理 图像处理 数
  • Cordova App 打包全揭秘

    运营专题 零预算引爆个人和企业品牌 原文链接 Selenium 自动化测试从零实战 原文链接 原来这样做 xff0c 才能向架构师靠近 原文链接 TensorFlow on Android xff1a 物体识别 原文链接 TensorFlo
  • Selenium 自动化测试从零实战

    运营专题 零预算引爆个人和企业品牌 原文链接 原来这样做 xff0c 才能向架构师靠近 原文链接 Cordova App 打包全揭秘 原文链接 TensorFlow on Android xff1a 物体识别 原文链接 TensorFlow

随机推荐

  • [运营专题]零预算引爆个人和企业品牌

    文章推荐 Selenium 自动化测试从零实战 原文链接 原来这样做 xff0c 才能向架构师靠近 原文链接 Cordova App 打包全揭秘 原文链接 TensorFlow on Android xff1a 物体识别 原文链接 Tens
  • 图解敏捷教练和 ScrumMaster

    运营专题 零预算引爆个人和企业品牌 原文链接 Selenium 自动化测试从零实战 原文链接 原来这样做 xff0c 才能向架构师靠近 原文链接 Cordova App 打包全揭秘 原文链接 TensorFlow on Android xf
  • TensorFlow on Android:物体识别

    运营专题 零预算引爆个人和企业品牌 原文链接 Selenium 自动化测试从零实战 原文链接 原来这样做 xff0c 才能向架构师靠近 原文链接 Cordova App 打包全揭秘 原文链接 TensorFlow on Android xf
  • Jetson Xavier NX can通信,拒绝误导!

    概述 最近在开发一个网关项目 xff0c 需要调试一些通信接口 网关底板是基于jetson xavier nx设计的 xff0c 所以硬件上许多相似 xff0c 所以我也会基于jetson xavier nx去做一些接口的测试 xff0c
  • TensorFlow on Android:训练模型

    文章推荐 运营专题 零预算引爆个人和企业品牌 原文链接 Selenium 自动化测试从零实战 原文链接 原来这样做 xff0c 才能向架构师靠近 原文链接 Cordova App 打包全揭秘 原文链接 TensorFlow on Andro
  • 深入理解 MySQL 底层实现

    本文来自作者 默默 在 GitChat 上分享 深入理解 MySQL 底层实现 xff0c 阅读原文 查看交流实录 文末高能 编辑 哈比 MySQL 的常用引擎 1 InnoDB InnoDB 的存储文件有两个 xff0c 后缀名分别是 f
  • 修改订单金额!?0.01 元购买 iPhoneX?| Web谈逻辑漏洞

    本文来自作者 肖志华 在 GitChat 上分享 Web 安全恩仇录 xff1a 再谈逻辑漏洞 xff0c 阅读原文 查看交流实录 文末高能 编辑 哈比 Web 漏洞里有 SQL 注入 XSS 等漏洞 xff0c 但是逻辑漏洞等问题也是一个
  • 如何开发一款游戏:游戏开发流程及所需工具

    本文来自作者 goto先生 在 GitChat 上分享 如何开发一款游戏 xff1a 游戏开发流程及所需工具 编辑 哈比 游戏作为娱乐生活的一个方面 xff0c 参与其中的人越来越多 xff0c 而大部分参与其中的人都是以玩家的身份 他们热
  • 如何用 Python 做自动化测试

    本文来自作者 Vant 在 GitChat 上分享 如何用 Python 做自动化测试 编辑 哈比 一 Selenium 环境部署 1 window 环境部署 1 1 当前环境 Win10 64 位系统 xff1b Python3 6 2
  • 我真的要做一辈子的程序员吗?

    本文来自作者 王鹏 在 GitChat 上分享 程序员跳槽时 xff0c 如何正确做好职业规划 xff1f 阅读原文 查看交流实录 编辑 贝吉塔 经常听一些同学说 xff1a 不知道下一份工作该去哪类公司做些什么 xff0c 我的职场人际一
  • 20万到100万年薪的算法工程师有什么区别?

    公元七世纪 xff0c 在车迟国国家气象局组织的一次求雨活动中 xff0c 虎力 鹿力 羊力三位大仙成功地祈下甘霖 xff0c 救黎民于水火中 老国王虽然不明就里 xff0c 却从此尊他们为国师 xff0c 奉道教为圭臬 本世纪 xff0c
  • 在win7下安装ubuntu

    安装VMware 从官网下载VMware workstation full 10 0 4 2249910 exe文件 xff0c 一直点击下一步 xff0c 直到安装成功 安装成功的界面如图所示 xff1a 安装Ubuntu 1 获取发行版
  • 带参数的宏定义 #define

    main c C语言学习 include lt stdio h gt 求和函数 int sum int v1 int v2 return v1 43 v2 宏定义 define 代表要定义一个宏 SUM 宏的名称 v1 v2 参数 xff0
  • 串口编程项目— PX4FLOW 传感器数据接收处理(英文paper直接贴过来)

    写在前面 xff1a 七月份初 xff0c 结束交流生项目 xff0c 向导师申请了个项目 xff0c 关于PX4FLOW传感器的 xff0c serial programming 的java编程 xff0c 下午刚刚提交完paper 写个
  • 电路城(www.cirmall.com)-基于HS1101LF和TC1047A 温湿度传感器模块(硬件+源码+设计说明等)

    电路城 xff08 www cirmall com xff09 本次分享的温湿度传感器模块采用瑞萨电子生产的 R7F0C802 单片机作为控制单元 xff0c 采集温度传感器 TC1047A 输出的电压信号和湿度传感器HS1101LF 电路
  • 服务器后端开发,学什么编程语言?

    服务器后端开发 xff0c 学什么编程语言 xff1f 技术 后台开发 xff0c 目前主流的编程语言是Java xff0c Go语言 xff0c C 43 43 xff0c python 以及php 个人建议学Java xff0c 主流中
  • ROS基础:功能包的创建与编译

    创建 如果你使用的是catkin编译系统 xff0c 那么可以很容易地创建一个功能包 首先进入你的工作空间的src目录下 xff1a cd catkin ws src 然后运行catkin create pkg命令 xff1a catkin
  • 关于TF转换信息(Transforms)的理解

    本文主要内容参考自ROS wiki tf Overview Transformations xff0c 在加入了自己的一些理解的同时 xff0c 我也对原文进行了适当的修改 原文使用Creative Commons Attribution
  • RealSense SDK 开发笔记(一)环境配置

    转载请注明出处 2017 以后的SDK结构必然趋于稳定 xff0c 也就会多一些新特性和新设备的支持 xff0c 配置流程不再变化 2016 2 xff1a 目前SDK版本是2016R1 xff0c 配置过程一模一样 xff0c SDK主要
  • 闭包方法的学习

    相关链接 swift中使用 64 noescape的正确姿势 文档闭包 闭包可以捕获和存储其在上下文中任意常量和变量的引用 被称为包裹常量和变量 Swift会为你管理在捕获过程中涉及到的所有内存操作 在函数章节中介绍的全局和嵌套函数也是特殊