Swift中转义闭包示例详解

 更新时间:2021年11月03日 16:32:21   作者:小小小_小朋友  
在Swift 中的闭包类似于结构块,并可以在任何地方调用,下面这篇文章主要给大家介绍了关于Swift中转义闭包的相关资料,需要的朋友可以参考下
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

前言

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

相关文章

  • Flutter iOS开发OC混编Swift动态库和静态库问题填坑

    Flutter iOS开发OC混编Swift动态库和静态库问题填坑

    这篇文章主要为大家介绍了Flutter iOS OC 混编 Swift动态库和静态库问题填坑详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Swift如何在应用中添加图标更换功能的方法

    Swift如何在应用中添加图标更换功能的方法

    本篇文章主要介绍了Swift如何在应用中添加图标更换功能的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • Swift 4.2使用self做为变量名浅析

    Swift 4.2使用self做为变量名浅析

    这篇文章主要给大家介绍了关于Swift 4.2使用self做为变量名的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09
  • Swift NavigationBar隐藏后的右滑手势效果

    Swift NavigationBar隐藏后的右滑手势效果

    这篇文章主要为大家详细介绍了Swift NavigationBar隐藏后的右滑手势效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • Swift教程之类与结构详解

    Swift教程之类与结构详解

    这篇文章主要介绍了Swift教程之类与结构详解,本文讲解了类和结构的异同、结构和枚举类型是数值类型、类是引用类型、如何选择使用类还是结构、集合类型的赋值和复制操作等内容,需要的朋友可以参考下
    2015-01-01
  • Swift中动态调用实例方法介绍

    Swift中动态调用实例方法介绍

    这篇文章主要介绍了Swift中动态调用实例方法介绍,在Swift中有一类很有意思的写法,可以让我们不直接使用实例来调用这个实例上的方法,而是通过类型取出这个类型的某个实例方法的签名,然后再通过传递实例来拿到实际需要调用的方法,需要的朋友可以参考下
    2015-01-01
  • Swift语言中字符串相关的基本概念解析

    Swift语言中字符串相关的基本概念解析

    这篇文章主要介绍了Swift语言中字符串相关的基本概念解析,是Swift入门学习中的基础知识,需要的朋友可以参考下
    2015-11-11
  • 详解Swift编程中下标的用法

    详解Swift编程中下标的用法

    这篇文章主要介绍了Swift编程中下标的用法,是Swift入门学习中的基础知识,需要的朋友可以参考下
    2015-11-11
  • Swift中定义单例的方法实例

    Swift中定义单例的方法实例

    Swift中单例的写法有很多种,下面这篇文章主要给大家介绍了关于Swift中定义单例的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2018-03-03
  • swift guard关键字详解及使用

    swift guard关键字详解及使用

    这篇文章主要介绍了swift guard关键字详解及使用的相关资料,需要的朋友可以参考下
    2017-06-06

最新评论

?


http://www.vxiaotou.com