在下面的示例中,测试闭包作为函数参数传入,不需要@sescape。这是否意味着它被视为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)
这是否意味着它算作noescape闭包?
不,当然不是。它转义传递给它的函数的生存期(在本例中为init(c:)
),因此根据定义,它是转义的。您不必将其标记为@escaping
,因为只有闭包函数参数(即类型为函数本身的函数参数)在默认情况下是不转义的(根据SE-0103)。
因此,因为闭包是转义的,所以它需要捕获的任何状态都进行堆分配。这种状态不可能被堆栈分配,因为它的生存期不限于当前堆栈帧。
然而,在您的情况下,您只是传递了一个简单的全局函数test
。这里不需要进行额外的堆分配,因为全局函数是静态存储的,因此只需要传递一个简单的指针。
尽管值得注意的是,因为您使用的是泛型,为了将函数键入为给定的泛型占位符T
,Swift将使用reabstration thunk来统一调用约定。要做到这一点,将使用堆分配框来存储函数值(我将在本问答中对此进行更详细的介绍)。
然而,在优化的构建中,thunk在某些情况下似乎能够专门化(例如直接使用顶级函数),因此意味着不需要进行额外的堆分配。
这是否意味着它算作noescape闭包?
您的通用版本欺骗了编译器。
编译器似乎会查找任何类型为闭包并在方法中使用的方法参数。不检查它是如何使用的,例如,如果它实际上被称为:
class Test {
var closure: Any
init(c: ()->Void) {
self.closure = c //Error: Non-Ecaping parameter 'c' may only be called
}
}
通过将闭包设置为泛型类型(T
),它忽略了T
可能是闭包的事实。如果它不忽视这一点,它就必须抱怨每一个通用的论点,因为这可能是一个结束。
我想知道这是否是一种解决方案,可以避免由于转义而导致的堆分配。
基于意见:编译器的警告和错误是有原因的。我恳求不要抑制它们,尤其是出于过早优化的原因,比如"解决问题以避免堆分配"。