我有一个奇怪的情况。我在函数中有一些局部变量:
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不太熟悉,无法解决这个问题。为什么会产生编译时错误以及如何解决?
编辑:似乎如果我执行以下操作,我不会再遇到编译时错误,但这对我来说没有任何意义,这总是一件坏事,所以我仍然想要一个解释... < / p>id foo = ^() {
jsval localSuccessCb = successCb;
JS_RemoveValueRoot(cx, &localSuccessCb);
};
答案 0 :(得分:1)
啊我相信这是个问题。来自this article on closures:
这是第一个区别。通过闭包在块中可用的变量被输入为“const”。这意味着无法从块内部修改它们的值。
因此,错误是我传递了JS_RemoveValueRoot
一个const jsval *
而不是jsval *
。创建一个非常量的本地副本“解决”了这个问题(取决于该行为是否可接受,在这种情况下是这样)。
或者我也可以将jsval
声明为:
__block jsval successCb = ...;
在这种情况下,我不必创建本地非常量副本。
在这种情况下,XCode确实提供了无用的错误消息......
答案 1 :(得分:1)
那更复杂。是的,当前的问题是所有非__block
捕获的变量都在块内const
。因此,块cx
内的类型为JSContext * const
,successCb
的类型为const jsval
。并且const jsval *
无法传递给jsval *
。但您必须先了解为什么变量为const
。
阻止在创建时按值捕获非__block
变量。这意味着块内部变量的副本和外部副本是不同的独立变量,即使它们具有相同的名称。如果它不是const
,您可能会想要更改块内的变量并期望它在外部更改,但事实并非如此。 (当然,相反的问题仍然存在 - 您仍然可以更改块外的变量,因为它不是const
,并且想知道为什么它不会在块内部发生变化。)__block
解决了这个问题通过使它只有一个变量的副本,在块的内部和外部共享。
然后考虑为什么 const
变量是不够的,这一点很重要。如果您只需要变量的值,那么const
副本也是如此。当const
不起作用时,通常是因为需要分配给变量。我们需要问一下,JS_RemoveValueRoot
它需要一个非const
指向变量的指针是什么?是分配给变量吗? (如果确实如此,我们是否关心块外的新值?因为如果没有,我们可以将const
变量分配给块内的非const
变量。)
事实证明它更复杂。根据{{1}}的文档,它既不使用指向的变量的值,也不需要设置变量;相反,它需要变量的地址,这需要匹配传递给JS_Remove*Root
的地址。 (实际上,我甚至不确定他们正在做什么甚至是否需要JS_Add*Root
指针。)我假设const
在包围块的函数体内完成,在外面块。 (我假设这是因为你说JS_AddValueRoot
是一个局部变量,所以它必须在这个函数内;如果它在块内,它就没有意义,因为那时successCb
可能只是一个本地块的变量,因此不需要捕获。)
因为变量本身的地址很重要,让我们考虑各种块变量捕获模式中会发生什么。非successCb
变量现在显然不合适,因为内部和外部有两个单独的副本(因此有两个单独的地址)。因此,__block
和Add
的地址不匹配。 Remove
变量是共享的,并且要好得多。
但是,__block
变量仍有问题可能导致其不匹配 - __block
变量的地址可能会随时间而变化!这涉及到如何实现块的细节。在当前实现中,__block
变量保存在从堆栈开始的特殊结构(一种“对象”)中,但是当捕获它的任何块被复制时,它被“移动”到堆作为动态分配的结构。这与捕获变量的块对象如何从堆栈开始,但在复制时移动到堆非常相似。首先将它放在堆栈上是一种优化,并不保证会发生;但目前确实如此。 __block
变量本身实际上是对此结构内变量的访问,通过指向该结构所在位置的指针访问。当结构从堆栈移动到堆时,您可以看到表达式__block
的值发生变化。 (这在正常的C中是不可能的。)因此,要获得匹配的地址,必须确保在将变量的地址传递给&successCb
时已经发生了移动。您可以通过强制复制捕获它的块来执行此操作。