1

我遇到了一个奇怪的情况。我在函数中有一些局部变量:

JSContext *cx = ...;
jsval successCb = ...;

有一个函数调用接受这些参数:

//JS_RemoveValueRoot(JSContext *cx, jsval *vp);
JS_RemoveValueRoot(cx, &successCb); //works

上面编译得很好。但是,如果我有以下内容,则会收到编译时错误:

id foo = ^() {
    JS_RemoveValueRoot(cx, &successCb);
}

从字面上看,如果我复制并粘贴该行,如果它在它编译的块之外,但如果不是,它不会。错误是:

No matching function for call to 'JS_RemoveValueRoot'

我怀疑在如何实现块闭包方面幕后发生了一些事情,但我对 Objective C 不够熟悉,无法弄清楚这一点。为什么这会产生编译时错误,我该如何解决?

编辑:似乎如果我执行以下操作,我不再收到编译时错误,但这对我来说毫无意义,这总是一件坏事,所以我仍然想要一个解释......

id foo = ^() {
    jsval localSuccessCb = successCb;
    JS_RemoveValueRoot(cx, &localSuccessCb);
};
4

2 回答 2

1

啊,我相信这是问题所在。从这篇关于闭包的文章

这是第一个区别。闭包块中可用的变量类型为«const»。这意味着它们的值不能从块内部修改。

因此错误是我传递的是JS_RemoveValueRootaconst jsval *而不是 a jsval *。创建一个非恒定的本地副本“解决”了问题(取决于该行为是否可以接受,在这种情况下是可以接受的)。

或者我也可以声明jsval为:

__block jsval successCb = ...;

在这种情况下,我不必创建本地非常量副本。

在这种情况下,XCode 确实提供了非常无用的错误消息......

于 2013-10-01T00:38:06.027 回答
1

那更复杂。是的,直接的问题是所有未__block捕获的变量都const在块内。因此,块内部cxhas typeJSContext * constsuccessCbhas type const jsval。并且const jsval *不能传递给jsval *. 但是您必须首先了解为什么变量是const.

块在创建它们时按值捕获非__block变量。这意味着块内部的变量副本和外部的副本是不同的独立变量,即使它们具有相同的名称。如果不是,您可能想更改块内的变量并期望它在外部更改,但事实并非如此。(当然,相反的问题仍然会发生——您仍然可以在块外更改变量,因为它不是,并且想知道为什么它不会在块内更改。)通过使其只有一个副本来解决此问题变量,在块的内部和外部之间共享。constconst__block

然后重要的是要考虑为什么一个const变量是不够的。如果您只需要变量的值,那么const副本也一样。什么时候const不起作用,通常是因为需要分配给变量。我们需要问,JS_RemoveValueRoot它需要一个const指向变量的非指针是什么?是分配给变量吗?(如果是,我们是否关心块外的新值?因为如果不关心,我们可以将const变量分配给块内的非const变量。)

事实证明它更复杂。根据 的文档JS_Remove*Root,它既不使用指向的变量的值,也不需要设置变量;相反,它需要变量的地址,这需要与传递给JS_Add*Root. (实际上,我什至不确定const他们正在做的事情是否需要指针。)我假设这JS_AddValueRoot是在包含块的函数体中完成的,在块之外。(我假设这是因为你说successCb的是​​一个局部变量,所以它必须在这个函数内;如果它在块内,那就没有意义了,因为 thensuccessCb可能只是块的局部变量,因此不需要被俘虏。)

因为变量本身的地址很重要,让我们考虑一下在各种块变量捕获模式下会发生什么。非__block变量现在显然不合适,因为内部和外部有两个单独的副本(因此有两个单独的地址)。因此,给定的地址AddRemove不匹配。变量是共享的__block,并且要好得多。

但是,变量仍然存在__block可能使其不匹配的问题——__block变量的地址可能会随着时间而改变!这涉及如何实现块的细节。在当前的实现中,一个__block变量被保存在一个特殊的结构(一种“对象”)中,该结构从堆栈开始,但是当任何捕获它的块被复制时,它被“移动”到堆作为动态-分配的结构。这与捕获变量的块对象在堆栈上开始的方式非常相似,但在被复制时被移动到堆中。先入栈是一种优化,不保证一定会发生;但目前确实如此。这__block变量本身实际上是对该结构内部变量的访问,通过跟踪该结构所在位置的指针进行访问。随着结构从堆栈移动到堆,您可以看到表达式的值发生了&successCb变化。(这在普通 C 中是不可能的。)因此,要获得匹配的地址,您必须确保在将变量的地址传递给Add. 您可以通过强制复制捕获它的块来执行此操作。

于 2013-10-29T09:45:12.573 回答