Objective-C选择器指针传递给C函数

17

我有一个包含函数指针的C结构体,在C中使用这个结构没有问题,但是现在我要在Objective-C中使用这个C结构体,并且需要传递一个在Objective-C类中定义的函数(或选择器)指针。

1. 这是我需要作为指针传递给C函数的Objective-C选择器:

- (void)myObjCSelector:(int*)myIntArray
{
    // Do whatever I need with myIntArray
}

2. 这里是我遇到困难的地方,我正在Objective-C中尝试将选择器作为指针传递给C函数调用: 在这个C函数调用中,我需要正确的语法来将选择器作为函数指针传递,替换"myObjCSelectorPointer"。

passObjCSelectorPointerToCContext(cContextReference, myObjCSelectorPointer);

我进行了调查,但主要找到了几种做类似事情的不同方法,但我没有找到针对调用C函数并传递Objective-C选择器指针的特定内容。


2
这是一个非常好的问题,因为它强调了一个普遍的误解,即选择器是函数指针,这是错误的,并且被大多数新的Objective-C程序员所持有。 - twerdster
我真的非常感谢得到的所有答案,我也理解学习和理解选择器和函数之间差异的重要性。但是为了后人以及寻找相同问题的其他用户的清晰度和明智性,我正在寻找一个简洁的答案,直接涉及问题。我已经阅读了答案并查看了链接,但除非我漏掉了什么,否则我仍然无法找到重现和解决问题的步骤。 - Claudia
我已经根据第二次编辑和更加“简明扼要”的答案更新了我的回答。我想再次强调:这不是一个好主意。 - twerdster
@twerdster 感谢您的编辑!我已经阅读了它,现在它确实更有意义了。我明天会测试一下。但需要注意的是,使用指针的这个C函数位于一个库中,我无法对其进行编辑。而且这不是我可以用Objective-C重写的库。所以我基本上被卡住了。听到专家们说这不是一个好主意,这确实让我很紧张。无论如何,非常感谢您的帮助。 - Claudia
抱歉,我的表述确实不够清晰。我想知道的是传递 passObjCSelectorPointerToCContext 函数参数时每个参数的类型。那么 cContextReferences 的类型是什么?它是指向特殊结构体的指针吗?如果是,那么这个结构体的组成部分是什么? - twerdster
显示剩余2条评论
3个回答

19
在objc中,选择器(selector)不是函数指针。选择器是一个唯一的整数,在由objc运行时存储的方法查找表中映射到一个字符串。在上面的例子中,你的方法名将是myObjCSelector:,要获取它的唯一选择器,你需要键入@selector(myObjCSelector:)。然而,这对你来说没有任何用处,因为它并不代表一个特定的函数实现。
你要找的是IMP,请参考这个SO问题。 编辑 2:
 IMP myObjCSelectorPointer = (void (*)(id,SEL,int*))[self methodForSelector:@selector(myObjCSelector:)];

然后您可以使用以下方法调用该方法

myObjCSelectorPointer(self,@selector(myObjCSelector:),myIntArray);

然而,这意味着你需要确保在c函数调用passObjCSelectorPointerToCContext时将self指针添加进去。因此,它应该看起来像这样:

passObjCSelectorPointerToCContext(cContextReference, self, myObjCSelectorPointer); 
当从包含该方法的对象内部调用时。
需要注意的是,几乎从不使用 IMP 是正确的技术选择。应该尽量坚持纯 Obj-C。由于 Obj-C 使用临时缓存,因此在第一次调用消息后,Obj-C 是相当高效的。
编辑1:
了解 objc 为什么以这种方式工作非常有用。苹果文档提供了深入的解释。但简短的解释如下:当您向对象发送消息,例如[myobject somemethod],编译器不会立即知道要调用哪个特定实现的 somemethod,因为可能有多个类具有多个重写版本的 somemethod。所有这些方法都具有相同的选择器,而与其参数和返回值无关,因此对于 somemethod 的哪个实现的决策被推迟到程序运行时执行[myobject somemethod]被编译器转换为 C 函数调用:
objc_msgSend(myobject, @selector(somemethod))
这是一个特殊的函数,它会搜索每个myobject类的布局,以查看该类是否知道如何响应somemethod消息。如果不知道,它会继续搜索该类的父类,直到根类。如果没有任何类能够响应somemethod,则NSObject定义了一个名为forward的私有方法,用于接收所有未知的消息。

假设某个类能够响应somemethod消息,则它还将具有指向该方法实际实现的类型为IMP的特定指针。此时将调用该方法。

这个过程比我描述的要复杂得多,但这个概述应该足以帮助您理解选择器的目的。

最后一点是,方法名被映射到唯一整数的原因是因为运行时不必浪费时间进行字符串比较。

+1 因为你是绝对正确的,但-1因为我认为直接使用原始的 IMP 不是最好的方法来处理这个问题。而直接调用 objc_msgSend 几乎从来都不是一个好主意。 - Dave DeLong
所以它们被取消了 ;) 我同意直接使用IMP几乎从来不是最好的方法。我已经相应地更新了。 - twerdster

1

你必须使用函数指针吗?在 Objective-C 中,可以获取任意方法实现的函数指针(称为 IMP),但这非常不常见,通常也不是一个好主意。 直接调用 objc_msgSend() 也不是最好的选择,因为有几种不同版本的 objc_msgSend(),编译器会基于方法的返回类型自动选择不同的版本。 返回对象的方法经过 objc_msgSend(),但返回结构体的对象可能经过 objc_msgSend() 或者经过 objc_msgSend_stret()。而如果该方法返回 double,则经过 objc_msgSend_fpret()...

文档: Objective-C Runtime 参考:发送消息

相反,我建议使用目标操作对或使用块。 然后,您可以做如下操作:

myContextRef->target = anObjcObject;
myContextRef->action = @selector(invokeMe:);

完成后,执行:

[myContextRef->target performSelector:myContextRef->action withObject:someReturnInformation];

或者可以使用块:

myContextRef->completionHandler = [^(id returnInformation) {
  [anObjcObject invokeMe:returnInformation];
} copy];

然后当你完成时,执行:

myContextRef->completionHandler(someReturnInformation);

(不要忘记在释放上下文时-release块)

1

基本上,答案是:Objective-C 选择器不同于函数指针。执行选择器需要两个数据部分。这是一个对象和选择器本身。你需要一些胶水来完成你的任务。

查看this问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接