我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Swift中转义闭包示例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Swift中转义闭包示例详解

前言

Swift 是一种非常强大的编程语言,是为 Apple 生态系统开发应用程序的首选;iOS、macOS、watchOS 和 tvOS。作为使用 Swift 编写代码的开发人员,我们经常使用闭包;语言的一个重要而重要的章节。

闭包不是初学者开始的主题。然而,这是每个人都必须尽快了解的东西。有很多方面需要了解并了解它们的工作原理。在所有这些中,有一个特定的;转义闭包和@escaping属性。在这篇文章中,我将尽可能简单地解释它们是什么以及它们可能带来的附带影响。

转义与非转义闭包

在谈论转义闭包时,我们总是指作为函数或方法参数提供的闭包。一般来说,我们将提供给方法(或函数)的闭包分为两类:

  1. 在方法执行完成之前调用的闭包。
  2. 在方法执行完成后调用的闭包。

在后一种情况下,我们谈论的是转义闭包;关闭该继续即使电子住后的的xecution方法,直到我们在以后的任何时间在未来给他们打电话。

在前一种情况下,与我上面描述的完全相反,我们称闭包为non-escaping。

直到 Swift 3,默认情况下,所有作为参数传递给方法或函数的闭包都被认为是转义的。自 Swift 3 以来,这不再正确;默认情况下,所有方法都被认为是非转义的,这意味着它们在方法执行完成之前被调用。

以下代码部分演示了一个非转义闭包:


func  add(num1:  Double,

 num2:  Double,

 completion:  (_  result:  Double)  ->  Void)  {

    let  sum  =  num1  +  num2

    completion(sum)

}

所述completion封闭件之前执行代码叶调用的方法,所以这不是一个逸出闭合的情况。

然而,一个闭包是如何从一个方法中逃脱的,所以我们最终得到了与上述情况相反的结果?

逃离方法

为了使闭包成为转义闭包,有必要将对其的引用保留在方法的范围之外,以便我们稍后使用它。看看下面的代码:


class  Demo  {  
    var  result:  Double?
    var  resultHandler:  (()  ->  Void)?
    func  add2(num1:  Double,
              num2:  Double,
              completion:  ()  ->  Void)  {
        resultHandler  =  completion
        result  =  num1  +  num2
    }
}

这里我们有一个result属性,它保存在方法内部发生的加法的结果。但我们也resultHandler有财产;this 保持对completion作为方法参数提供的闭包的引用。

闭包通过以下行从方法中转义:


resultHandler  =  completion

然而,这不是唯一需要的操作,所以我们可以说这completion是一个转义闭包。我们必须明确指出编译器,否则我们将在 Xcode 中看到以下错误:

为了修复它,我们需要用@escaping属性标记闭包。我们将此属性放在闭包名称和分号之后,但在闭包类型之前,如下所示:


func  add2(num1:  Double,
          num2:  Double,
          completion:  @escaping  ()  ->  Void)  {
    ...
}

编译器不再抱怨,completion现在正式成为转义闭包。

将转义关闭付诸行动

让我们在上面的Demo类中再添加两个方法;一个将调用add2(num1:num2:completion:)方法,另一个将调用resultHandler闭包以获得最终结果:


class  Demo  {
    ...
    func  doubleSum(num1:  Double,
                    num2:  Double)  {
        add2(num1:  num1,  num2:  num2)  {
            guard let  result  =  self.result  else  {  return  }
            self.result  =  result  *  2
        }
    }

    func  getResult()  {
        resultHandler?()
    }
}

第一种方法将 add 方法计算的结果加倍。但是,该结果不会翻倍,并且在add2(num1:num2:completion:)我们调用该getResult()方法之前,不会执行该方法中闭包主体内的代码。

这是我们从转义闭包中受益的地方;我们可以在我们的代码中需要的时候以及在合适的时机触发闭包的调用。尽管提供的示例故意过于简单,但在实际项目中,实际优势会变得更加明显和大胆。

注意强参考周期

让我们为Demo类添加最后一个,并实现默认的初始化器和析构器方法:


class  Demo  {
    init()  {
        print("Init")
    }
    deinit  {
        print("Deinit")
    }
    ...
}

init()是Demo初始化实例时调用的第一个方法,deinit也是释放实例之前调用的最后一个方法。我向它们都添加了一个打印命令,以验证它们是否被调用,并且在使用带有上述转义闭包的方法时没有内存泄漏。

注意:当我们尝试通过将对象设置为 nil 来释放它时,可能存在内存泄漏,但该对象仍保留在内存中,因为该对象与其他保持其活动状态的对象之间存在强引用。

现在,让我们添加以下几行来使用上述所有内容:


var  demo:  Demo?  =  Demo()
demo?.doubleSum(num1:  5,  num2:  10)
demo?.getResult()
print((demo?.result!)!)
demo  =  nil

首先,我们初始化类的一个可选实例Demo,以便稍后我们可以将其设为 nil。然后,我们调用该doubleSum(num1:num2:)方法以将作为参数给出的两个数字相加,然后将该结果加倍。但是,正如我之前所说的,在我们调用该getResult()方法之前不会发生这种情况;在方法中实际调用转义闭包的那个add2(num1:num2:completion:)。

最后,我们打印实例中result属性的值demo,并将其demo设为 nil。

*注意:*如上面的代码片段所示,使用感叹号 (!) 强制展开可选值是一种非常糟糕的做法,请不要这样做。我在这里这样做的唯一原因是为了让事情尽可能简单。

以上行将打印以下内容:

Init

30.0

请注意,此处缺少“Deinit”消息!也就是说deinit没有调用该方法,证明制作demo实例nil没有实际结果。看起来,只需几行简单的代码,我们就设法解决了内存泄漏问题。

内存泄漏背后的原因

在我们找到解决内存泄漏的方法之前,有必要了解它发生的原因。为了找到它,让我们退后几步来修改我们之前所做的。

首先,我们使用以下completion行使闭包从方法中逃逸:


resultHandler  =  completion

这条线比看起来更“有罪”,因为它创建了对闭包的强烈引用completion。

注意:闭包是引用类型,就像类一样。

然而,仅凭这一点还不足以产生问题,因为释放demo实例会删除对闭包的引用。真正的麻烦始于doubleSum(num1:num2:)方法内部的闭包主体。

在那里,我们这次通过在使用对象访问属性时捕获**对象来创建另一个从闭包到demo实例的强引用:selfresult


guard let  result  =  self.result  else  {  return  }
self.result  =  result  *  2

当它们都到位时,Demo 实例保持对闭包的强引用,而闭包则是对实例的强引用。这会创建一个保留循环,也称为强引用循环。发生这种情况时,每个引用类型都会使另一个引用类型在内存中保持活动状态,因此它们最终都不会被释放。

请注意,这仅发生在包含带有转义闭包的方法的类中。structs 的情况有所不同,因为它们不是引用而是值类型,并且显式引用self不是强制性的。

消除强引用循环

有两种方法可以避免强引用循环,从而避免内存泄漏。第一个是在我们调用闭包后手动且显式地释放对闭包的引用:


func  getResult()  {
    resultHandler?()
    // Setting nil to resultHandler removes the reference to closure.
    resultHandler  =  nil
}

第二种方法是在闭包的主体中弱**捕获self实例:


func  doubleSum(num1:  Double,
 num2:  Double)  {
    add2(num1:  num1,  num2:  num2)  {  [weak  self]  in
        guard let  result  =  self?.result  else  {  return  }
        self?.result  =  result  *  2
    }
}

[weak self] in在关闭打开后查看添加。有了这个,我们建立了对 Demo 实例的弱引用,因此我们避免了保留循环。请注意,我们将self用作可选值,并在其后加上问号 (?) 符号。

没有必要应用这两种更改以避免强引用循环。无论我们最终选择哪一个,从现在开始,输出也将包含“Deinit”消息。这意味着该demo对象变为 nil,并且我们不再有内存泄漏。

Init

30.0

Deinit

概括

离开这里需要带上一件事,那就是在使用转义闭包时要小心。无论您是实现自己的接受转义闭包作为参数的方法,还是使用具有转义闭包的 API,请始终确保不会以强引用循环结束。在开发应用程序时,内存泄漏是一个很大的“禁忌”,我们当然不希望我们的应用程序在某个时候崩溃或因此被系统终止。另一方面,不要犹豫使用转义闭包;它们提供了可以产生更强大代码的优势。

到此这篇关于Swift中转义闭包的文章就介绍到这了,更多相关Swift转义闭包内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Swift中转义闭包示例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Swift中如何实现转义闭包

这篇文章主要为大家展示了“Swift中如何实现转义闭包”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Swift中如何实现转义闭包”这篇文章吧。转义与非转义闭包在谈论转义闭包时,我们总是指作为函数
2023-06-25

Swift 并发修改Sendable 闭包实例详解

这篇文章主要为大家介绍了Swift 并发修改Sendable 闭包实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Swift中的HTTP模拟测试示例详解

这篇文章主要为大家介绍了Swift中的HTTP模拟测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-06

Swift中的HTTP请求体RequestBodies使用示例详解

这篇文章主要为大家介绍了Swift中的HTTP请求体RequestBodies使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-03

Vue2 Observer实例dep和闭包中dep区别详解

这篇文章主要为大家介绍了Vue2 Observer实例dep和闭包中dep区别详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Golang中context包使用场景和示例详解

Golangcontext包使用场景Golangcontext包提供了一种机制,可以在goroutine之间传递上下文信息,包括取消信号、截止日期和值。它可用于处理各种场景,例如:取消操作设置截止日期传递值具体示例:取消操作:import"context"ctx,cancel:=context.WithCancel(context.Background())//取消上下文cancel()设置截止日期:import"context"ctx,cancel:=context.WithTimeout(contex
Golang中context包使用场景和示例详解
2024-04-23

Golang中context包使用场景和示例详解

这篇文章结合示例代码介绍了context包的几种使用场景,文中有详细的代码示例,对学习或工作有一定的帮助,需要的朋友可以参考下
2023-05-19

Go语言中io包核心接口示例详解

目录前言ReaderWriterCloserSeeker组合接口总结前言 IO 操作是我们在编程中不可避免会遇到的,例如读写文件,Go语言的 io 包中提供了相关的接口,定义了相应的规范,不同的数据类型可以根据规范去实现相应的方法,提供更加
2022-06-07

.NET 中配置从xml转向json方法示例详解

这篇文章主要为大家介绍了.NET 中配置从xml转向json方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录