在泛型类型数据中使用闭包是否会被转义?

时间:2017-01-19 02:54:12

标签: swift swift3

在下面的示例中,测试闭包作为函数参数传入,它不需要@escaping。这是否意味着它被视为noescape闭合?我想知道这是否可以避免因转义而导致堆分配。

func test() {
    print("hello")
}

class b<T> {
    let closure: T
    // does not requires init(c: @escaping () -> Void)
    init(c: T) {
        self.closure = c
    }
}

var c = b(c: test)

2 个答案:

答案 0 :(得分:2)

  

这是否意味着它被视为noescape闭合?

不,当然不是。它会转移传递给它的函数的生命周期(在这种情况下为init(c:)) - 因此按照定义,它正在转义。您只需将其标记为@escaping,因为闭包函数参数(即作为函数本身键入的函数参数)默认情况下是非转义的(根据{{3 }})。

因此,因为闭包是转义的,所以它需要捕获的任何状态为堆分配。这种状态不可能是堆栈分配的,因为它的生命周期不限于当前的堆栈帧。

但是在你的情况下,你只是传递一个简单的全局函数test。由于全局函数是静态存储的,因此不需要在此处进行额外的堆分配,因此只需要传递一个简单的指针。

虽然值得注意的是,因为您正在使用泛型,为了将函数键入为给定的通用占位符T,Swift将使用reabstraction thunk以便统一调用约定。为此,将使用堆分配的框来存储函数值(我在SE-0103中更详细地介绍了这一点。)

但是,在优化构建中,在某些情况下this Q&A(例如直接使用顶级函数),因此意味着不需要额外的堆分配。

答案 1 :(得分:1)

  

这是否意味着它被视为noescape闭合?

您的通用版本会欺骗编译器。

编译器似乎在寻找任何类型为closure的方法参数,并在方法中使用。不检查它是如何使用的,例如,如果它实际上被称为:

class Test {
    var closure: Any
    init(c: ()->Void) {
        self.closure = c //Error: Non-Ecaping parameter 'c' may only be called
    }
}

通过将闭包设置为泛型类型(T),它忽略了T可能是闭包的事实。如果它不会忽略这一点,它就会对每个泛型参数抱怨,因为它可能是一个闭包。

  

我想知道这是否是一种解决方法,以避免因转义而导致堆分配。

基于意见:编译器警告和错误是有原因的。我反对压制它们,特别是因为过早的优化原因,例如&#34;努力避免堆分配&#34;。