【问题标题】:Why does Swift 3 need @escaping annotation at all?为什么 Swift 3 完全需要 @escaping 注解?
【发布时间】:2017-07-20 17:47:09
【问题描述】:

我阅读了这篇question and answersCocoacasts blog post 并且完全理解@escaping 注释是什么。

但老实说,我完全不明白我们为什么需要它。

上述 Cocoacasts 博客文章指出:

默认情况下使闭包不转义有几个好处。 最明显的好处是性能和能力 编译器来优化你的代码。如果编译器知道一个闭包 是不可逃避的,它可以处理许多细节 内存管理的细节。

但是 Swift 编译器可以确定 @escaping 是否丢失并在这种情况下显示错误,所以如果我们从 Swift 语言中删除 @escaping 注释,那么编译器仍然可以看到闭包没有逃逸,我们可以让他应用优化在那种情况下。

这也意味着您可以使用 self 关键字而不会出现问题 非转义闭包,因为闭包在 函数返回。不需要对 self 使用弱引用 关闭。这是您免费获得的一项不错的福利。

但是如果闭包参数被标记为@escaping,我仍然可以传递使用强引用self 的闭包,并且编译器不会显示任何警告。实际上,如果在 @escaping 闭包中捕获的所有引用默认为 weak 并应用特殊关键字使其更强大,这将更有用。

我还认为@escaping 注释是通过明确声明此闭包参数不会转义函数体而使代码自我记录的方法,但调用方的目的是什么?它不限制闭包定义的方式,也不阻止调用方使用强引用。所以我剩下的就是希望调用方仔细查看函数签名并采取适当的措施(比如使用弱引用)。

所以问题是为什么我们真的需要 Swift 3 中的 @escaping 闭包,在哪些情况下我们不能没有它?

更新

我知道不转义闭包不能传递给闭包参数标记为@escaping的函数:

func testNoEscape(f: () -> ()) {
    f()
}

var storeF: (() -> ())?

func testEscaping(f: @escaping () -> ()) {
    storeF = f
}

func tryPassNoEscapeToEscaping(f: () -> ()) {
    testEscaping(f: f)
}

导致编译错误:

passing non-escaping parameter 'f' to function expecting an @escaping closure

但这是 @escaping 闭包带来的唯一真正限制,它看起来像是围绕自身构建的,并没有带来任何其他好处。

更新 2

虽然我在上面正确地阐述了我的想法,但我的最后一个问题是不准确的。

真正的问题是,如果编译器可以自己检测转义闭包并且@escaping 注释不对参数值施加任何限制,为什么我们需要@escaping 注释?

在我看来,如果编译器不允许我们在转义闭包时做一些坏事,比如使用self 和其他强引用,那会更有用。或者,如果转义闭包是某种特殊类型,我们必须在使用转义闭包参数调用函数时提及:

func f(c: () -> ()) { // c is escaping from f somehow
    // ...
}

f escaping { // have to use `escaping` keyword 
    // ...
}

所以调用方不必查看f 签名就知道c 正在转义,因为如果它试图将非转义闭包作为转义闭包参数值传递,则会出现编译错误。

在当前的实现中,想要在其代码中使用f 的开发人员必须查看f 签名以了解c 会转义,这是不安全的,因为这要求任何最初编写此代码并对其进行修改的人以后一定要知道f签名的细节,这是不可靠的,这样的代码也不是自记录的。

我知道我的问题可能不适合 SO。对此感到抱歉。

如果是这样,如果我无法从使用 Swift 语言和编译器实现转义逻辑的人那里得到答案,我将在稍后关闭它。

【问题讨论】:

  • 我回答你的问题了吗?
  • Alexander 我们进行了非常有益的讨论,非常感谢,但我认为这个问题只有做出这个决定的 Swift 语言开发者才能完全回答。我现在正试图联系他们,并根据我们的讨论和@matt 的回答准备 Swift 提案,我稍后会回到这个问题。
  • 您已移动问题的目标帖子。由于它以目前的形式存在,它已经得到了回答。欢迎您尝试为您的新问题创建一个新帖子,但它几乎肯定会因为离题而被删除。 SO 不是讨论语言设计者决策细节的论坛。
  • 堆栈溢出不是这样工作的。你不能只问一个问题,得到它的回答,然后不接受这个答案,因为它引发了一个后续问题。我已经花时间引导您彻底回答您的原始问题,直到我们达到您提出后续问题的程度开发人员的想法是他们决定的背后。
  • 这个经过编辑的问题现在是句法细节和对设计师意图的猜测的毫无意义的组​​合。因此,我已投票结束。

标签: swift swift3 closures


【解决方案1】:

编译器可以查看您创建的代码,但无法查看您用作预编译框架一部分的代码(截至目前,由于 ABI 问题,没有 Swift 框架,但会有将来)。

也许他们可以将这种转义要求仅应用于“公共”功能,但看起来会有点不一致。最好只通过工具提示插入关键字。

【讨论】:

    【解决方案2】:

    非转义闭包可以做转义闭包不能做的事情。它们是完全不同的动物。

    例如,非转义闭包可以引用self 的属性,而无需明确表示self。这是因为,由于不可转义(即在收到后立即执行),因此不会以某种棘手的方式捕获 self 并导致保留周期。

    而且,非转义闭包可以关闭 inout 参数。但这对于转义闭包来说是没有意义的并且是不允许的。

    如果闭包似乎有时需要self有时不需要self有时允许关闭inout有时不允许。 @escaping 注释使此类规则区分清晰且一致。

    【讨论】:

    • 感谢您的回答。对于那些处理转义闭包的人来说它仍然有用,我赞成它。很抱歉,我以错误的方式提出了我的问题。
    • 这是我最近的另一个答案,其中在关闭是转义还是非转义之间明确确定了做某事的合法性:stackoverflow.com/a/42720770/341994
    【解决方案3】:

    也许还有其他原因,但主要是为了传达您的闭包将比您传递给它的函数的持续时间长。这会对您的程序行为产生非常复杂的影响,因此明确这一点很重要。

    此外,闭包的“转义”是公共 API 的一部分。编译器无法查看正在调用的已编译库。如果没有 @escaping 属性公开传达闭包无法逃脱的事实,编译器就无法从编译后的代码中自行推断出它。

    【讨论】:

    • 是的,但是这就像写一个注释“不要在这里传递零,因为程序会崩溃”。它不会阻止您将任何闭包传递给具有 @escaping 注释的闭包参数的函数,除非您在其他函数内部并且您的闭包来自传递给该函数的参数而没有 @escaping 注释。
    • @mixel 所以这里真的有两个问题在起作用。 1)“为什么关闭的可逃避性很重要?”和 2)“为什么有一个明确声明可逃避性的注释?”。您的帖子要求 #2,但没有 #1 就没有意义
    • @mixel 闭包是一种引用类型。当一个闭包正在转义时,它可以比被调用的函数寿命更长,并且可以无限期地存储,并且可以无限期地多次调用。如果闭包在声明闭包的函数的本地范围内捕获任何值类型,则会出现问题。如果闭包可以比本地范围更有效,那么这些本地值类型也必须比本地范围更有效。因此,必须将这些值类型装箱,将它们移到堆上,并引入引用计数
    • @mixel 这确实会增加一笔费用。如果可能的话,最好避免它。并且可以避免这种装箱过程,但前提是可以保证闭包永远不会逃脱。在这种情况下,可以保证在它捕获的本地变量超出范围之前创建、使用和释放闭包。因此,闭包可以安全地直接引用堆栈上的那些本地变量,而无需将它们装箱。
    • @mixel 这 2 个 cmets 回答 1)“为什么关闭的可逃避性很重要?” .我的回答是 2)为什么有一个明确声明可逃避性的注释?”,这是您直接提出的问题。归结为“因为有必要在编译的二进制文件范围之外传递可逃避性信息”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-29
    • 1970-01-01
    • 2018-10-30
    • 2017-01-07
    • 2016-12-28
    • 1970-01-01
    相关资源
    最近更新 更多