Swift中转义闭包示例详解

 更新时间:2021年11月3日 20:00  点击:1786 作者:小小小_小朋友

前言

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转义闭包内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

原文出处:https://juejin.cn/post/7025872329431334920

[!--infotagslink--]

相关文章

  • swift中利用runtime交换方法的实现示例

    这篇文章主要给大家介绍了关于swift中利用runtime交换方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。...2020-06-30
  • 全面理解JavaScript中的闭包

    将外部作用域中的局部变量封闭起来的函数对象称为闭包(Closure),被封闭起来的变量与封闭它的函数对象有相同的生命周期,这在JavaScript中比较难理解而且十分重要,接下来带大家一起来全面理解JavaScript中的闭包:...2016-05-14
  • Swift设置UILabel内边距的实例代码

    有时候,我们需要一个显示文字,又想这些文字与边界之间有自定义的边距,所以下面这篇文章主要给大家介绍了关于Swift设置UILabel内边距的相关资料,需要的朋友可以参考下...2021-10-14
  • swift中的@UIApplicationMain示例详解

    这篇文章主要给大家介绍了关于swift中@UIApplicationMain的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。...2020-06-30
  • Swift实现多个TableView侧滑与切换效果

    这篇文章主要为大家详细介绍了Swift实现多个TableView侧滑与切换效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-30
  • 详解swift中xcworkspace多项目管理

    给大家详细讲解了IOS开发中swift语言xcworkspace多项目管理的方法和介绍,一起参考一下。...2020-06-30
  • Swift 中如何使用 Option Pattern 改善可选项的 API 设计

    这篇文章主要介绍了Swift 中如何使用 Option Pattern 改善可选项的 API 设计,帮助大家更好的进行ios开发,感兴趣的朋友可以了解下...2020-10-23
  • 如何使用Swift来实现一个命令行工具的方法

    这篇文章主要介绍了如何使用Swift来实现一个命令行工具,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • Swift中的命名空间详解

    这篇文章主要给大家介绍了关于Swift中命名空间的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • 浅析Swift中struct与class的区别(汇编角度底层分析)

    这篇文章主要介绍了Swift中struct与class的区别 ,本文从汇编角度分析struct与class的区别,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-06-30
  • Swift缩放并填充图片功能的实现

    最近有一个需求,就是将图片先等比例缩放到指定大小,然后将空余出来空间填充为黑色,返回指定大小的图片。本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧...2021-11-02
  • Swift中优雅处理闭包导致的循环引用详解

    这篇文章主要给大家介绍了关于Swift中优雅的处理闭包导致的循环引用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Swift具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-30
  • C# 中闭包(Closure)详解

    这篇文章主要介绍了C# 中闭包(Closure)详解的相关资料,需要的朋友可以参考下...2020-06-25
  • Swift实现倒计时5秒功能

    这篇文章主要为大家详细介绍了Swift实现倒计时5秒功能,在“登录”和“注册”页面也有相似功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-30
  • Swift 使用 Observe 监测页面滚动的实现方法

    这篇文章主要介绍了Swift 使用 Observe 监测页面滚动的实现方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-06-30
  • Swift在控件中添加点击手势的方法

    这篇文章主要介绍了Swift在控件中添加点击手势的方法,本文讲解如何在tableview的headerview中添加点击手势的方法,需要的朋友可以参考下...2020-06-30
  • Swift如何使用类型擦除及自定义详解

    有很多地方会用到类型擦除,并且它们的作用的各不相同。下面这篇文章主要给大家介绍了关于Swift如何使用类型擦除及自定义的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下...2020-06-30
  • Swift利用CoreData如何存储多种数据类的通讯录

    这篇文章主要给大家介绍了关于Swift利用CoreData如何存储多种数据类的通讯录的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧。...2020-06-30
  • SwiftUI 中创建反弹动画的实现

    这篇文章主要介绍了SwiftUI 中创建反弹动画的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-30
  • Swift中动态调用实例方法介绍

    这篇文章主要介绍了Swift中动态调用实例方法介绍,在Swift中有一类很有意思的写法,可以让我们不直接使用实例来调用这个实例上的方法,而是通过类型取出这个类型的某个实例方法的签名,然后再通过传递实例来拿到实际需要调用的方法,需要的朋友可以参考下...2020-06-30