在这个例子中,THIS
不是对全局变量的引用;它在上面的函数中定义为 void 指针的强制转换inRefCon
:
static OSStatus renderInput(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
{
// Get a reference to the object that was passed with the callback
// In this case, the AudioController passed itself so
// that you can access its data.
AudioController *THIS = (AudioController*)inRefCon;
这是 C 中相当常见的模式。为了将回调传递给某些 API,以便它稍后可以调用您的代码,您需要同时传递一个函数指针和一个 void 指针。void 指针包含函数指针需要操作的任何数据。在您的回调中,您需要将其转换回指向实际类型的指针,以便您可以访问其中的数据。在这种情况下,示例的作者命名了强制转换指针THIS
,可能是为了使它看起来更面向对象,尽管这只是 C 语言并且THIS
没有特殊含义。
您问他们为什么将其分配给局部变量而不是THIS->sinPhase
到处使用。没有理由不能THIS->sinPhase
在任何地方使用;他们可能只是将其分配给局部变量phase
以节省打字时间。优化器在局部变量上比在通过指针传入的变量上做得更好的可能性很小,因为它可以对局部变量做出更多假设(特别是,它可以假设没有其他人在同时)。因此,使用局部变量循环可能会运行得稍微快一些,但如果不进行测试我不能确定;最可能的原因只是为了节省打字并使代码更具可读性。
下面是一个简单的例子,说明这样的回调 API 是如何工作的;希望这能让我们更容易理解回调 API 的工作原理,而无需同时尝试理解 Core Audio 中发生的其他事情。假设我想编写一个函数,将回调应用于整数 10 次。我可能会写:
int do_ten_times(int input, int (*callback)(int)) {
int value = input;
for (int i = 0; i < 10; ++i) {
value = callback(value);
}
return value;
}
现在我可以用不同的函数来调用它,如下所示add_one()
或times_two()
:
int add_one(int x) {
return x + 1;
}
int times_two(int x) {
return x * 2;
}
result = do_ten_times(1, add_one);
result = do_ten_times(1, times_two);
但是假设我希望能够添加或乘以不同的数字;我可以尝试为每个要相加或相乘的数字编写一个函数,但是如果代码中没有固定数字,而是基于输入,那么您就会遇到问题。您不能为每个可能的数字编写一个函数;您将需要传入一个值。所以让我们为回调添加一个值,并将do_ten_times()
该值传入:
int do_ten_times(int input, int (*callback)(int, int), int data) {
int value = input;
for (int i = 0; i < 10; ++i) {
value = callback(value, data);
}
return value;
}
int add(int x, int increment) {
return x + increment;
}
int times(int x, int multiplier) {
return x * multiplier;
}
result = do_ten_times(1, add, 3);
result = do_ten_times(1, times, 4);
但是,如果有人想编写一个随整数以外的变量变化的函数怎么办?例如,如果您想编写一个根据输入是负数还是正数添加不同数字的函数怎么办?现在我们需要传入两个值。同样,我们可以扩展我们的接口以传入两个值;但是我们最终需要传入更多的值,不同类型的值等等。我们注意到它do_ten_times
并不关心我们传入的值的类型;它只需要将它传递给回调,回调可以根据需要对其进行解释。我们可以使用 void 指针来实现这一点;然后回调将该 void 指针转换为适当的类型以获取值:
int do_ten_times(int input, int (*callback)(int, void *), void *data) {
int value = input;
for (int i = 0; i < 10; ++i) {
value = callback(value, data);
}
return value;
}
int add(int x, void *data) {
int increment = *(int *)data;
return x + increment;
}
int times(int x, void *data) {
int multiplier = *(int *)data;
return x * multiplier;
}
struct pos_neg {
int pos;
int neg;
};
int add_pos_neg(int x, void *data) {
struct pos_neg *increments = (struct pos_neg *)data;
if (x >= 0)
return x + increments->pos;
else
return x + increments->neg;
}
int i = 3;
result = do_ten_times(1, add, &i);
int m = 4;
result = do_ten_times(1, times, &m);
struct pos_neg pn = { 2, -2 };
result = do_ten_times(-1, add_pos_neg, &pn);
当然,这些都是玩具示例。在 Core Audio 案例中,回调用于生成音频数据的缓冲区;每次音频系统需要生成更多数据以保持流畅播放时都会调用它。通过 传递的信息void *inRefCon
用于跟踪您在当前缓冲区中到达正弦波的确切位置,因此下一个缓冲区可以从最后一个缓冲区停止的地方开始。