没有匹配函数来调用...但只在一个块内?

时间:2013-10-01 00:33:09

标签: objective-c closures objective-c-blocks

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

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);
};

2 个答案:

答案 0 :(得分:1)

啊我相信这是个问题。来自this article on closures

  

这是第一个区别。通过闭包在块中可用的变量被输入为“const”。这意味着无法从块内部修改它们的值。

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

或者我也可以将jsval声明为:

__block jsval successCb = ...;

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

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

答案 1 :(得分:1)

那更复杂。是的,当前的问题是所有非__block捕获的变量都在块内const。因此,块cx内的类型为JSContext * constsuccessCb的类型为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变量现在显然不合适,因为内部和外部有两个单独的副本(因此有两个单独的地址)。因此,__blockAdd的地址不匹配。 Remove变量是共享的,并且要好得多。

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