我正在使用Objective-C ++进行个人项目,并且我在全局范围内使用初始化程序发现了SEL
变量的奇怪行为。考虑一下这个可运行的Objective-C ++片段:
#import <Foundation/Foundation.h>
@interface Foo : NSObject
-(void)closeWindow;
@end
@implementation Foo
-(void)closeWindow { puts("closeWindow called"); }
@end
static SEL globalSelector = @selector(closeWindow);
void printSelectorInfo(id target, SEL sel) {
const char* name = sel_getName(sel);
BOOL responds = [target respondsToSelector:sel];
printf("selector=%p; name=%s; responds=%hhu\n", sel, name, responds);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
SEL localSelector = @selector(closeWindow);
Foo* foo = [[Foo alloc] init];
printSelectorInfo(foo, localSelector);
printSelectorInfo(foo, globalSelector);
[foo closeWindow];
[foo performSelector:localSelector];
[foo performSelector:globalSelector];
}
}
在正常的Objective-C中,全局变量必须具有由C指示的常量初始值设定项,因此static SEL globalSelector = @selector(closeWindow)
无效。这种限制在C ++中得到了提升,因此在Objective-C ++中得到了提升,并且编译没有问题。
这将是预期的输出:
selector =&lt; some address&gt ;;命名= closeWindow;响应= 1
selector =&lt; some address&gt ;;命名= closeWindow;响应= 1
closeWindow调用[3次]
这确实是我在Debug中得到的:
selector=0x7fff952d63a1; name=closeWindow; responds=1
selector=0x7fff952d63a1; name=closeWindow; responds=1
closeWindow called
closeWindow called
closeWindow called
然而,Release中出现问题:
selector=0x7fff952d63a1; name=closeWindow; responds=1
selector=0x100000eca; name=closeWindow; responds=0
closeWindow called
closeWindow called
2013-05-06 16:40:11.960 selectors[5048:303] *** NSForwarding: warning: selector (0x100000eca) for message 'closeWindow' does not match selector known to Objective C runtime (0x7fff952d63a1)-- abort
2013-05-06 16:40:11.964 selectors[5048:303] -[Foo closeWindow]: unrecognized selector sent to instance 0x100108240
2013-05-06 16:40:11.966 selectors[5048:303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Foo closeWindow]: unrecognized selector sent to instance 0x100108240'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff91116b06 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff904843f0 objc_exception_throw + 43
2 CoreFoundation 0x00007fff911ad40a -[NSObject(NSObject) doesNotRecognizeSelector:] + 186
3 CoreFoundation 0x00007fff9110502e ___forwarding___ + 414
4 CoreFoundation 0x00007fff91104e18 _CF_forwarding_prep_0 + 232
5 selectors 0x0000000100000e14 main + 234
6 libdyld.dylib 0x00007fff944a77e1 start + 0
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminate called throwing an exception
请注意,现在,虽然它们仍具有相同的名称,但选择器的地址不同,Foo
仅响应局部变量中的选择器。
有趣的是,这个问题似乎是名称敏感的。如果我将方法的名称更改为foo
,它可以正常工作。
我是否遗漏了某些内容或依赖未定义的行为?
答案 0 :(得分:0)
由于没有正式的Objective-C ++标准,我无法验证这是否应该是明确定义的行为。也就是说,我觉得这是在编译器错误的领域,所以我一直在使用它作为一种解决方法:
/* reuse same heading as question */
static SEL globalSelector = @selector(closeWindow);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Foo* foo = [[Foo alloc] init];
SEL localSelector = globalSelector;
if (![foo respondsToSelector:localSelector])
{
localSelector = sel_getUID(sel_getName(localSelector));
}
[foo performSelector:localSelector];
}
}
The guys at LLVM confirm that this is a bug使用Xcode 4.6.2附带的Clang版本,并在下一版本中修复它:
你遇到过这个版本的Xcode的错误。我可以确认一下 在Xcode的下一个版本中修复了bug。在下一个版本中和 -Os,printSelectorInfo全部内联,不会调用它。你注意到-Os是什么样的尾部调用优化 printSelectorInfo对你的bug来说应该是无关紧要的 注意到。
答案 1 :(得分:-1)
编译选择器(SEL)不是函数指针,它们是标识方法名称的对象。
<强> objc.h 强>
typedef struct objc_selector *SEL;
Apple的文档
方法和选择器
编译的选择器识别方法名称,而不是方法实现。例如,一个类的显示方法与其他类中定义的显示方法具有相同的选择器。这对于多态和动态绑定至关重要;它允许您向属于不同类的接收器发送相同的消息。如果每个方法实现有一个选择器,则消息与函数调用没有区别。
具有相同名称的类方法和实例方法被赋予相同的选择器。但是,由于它们各自的域名,两者之间没有混淆。除了显示实例方法之外,类还可以定义显示类方法。
当我将上述示例添加到我的示例应用程序行时,无法编译:
static SEL globalSelector = @selector(closeWindow);
error: initializer element is not a compile-time constant
我也收到警告
[foo performSelector:localSelector];
warning: performSelector may cause a leak because its selector is unknown
我没有收到任何关于
的警告[foo performSelector:@selector(closeWindow)];
我的理解是@selector(closeWindow)是一个编译时指令,将closeWindow标识为选择器,但是objc_selector结构的查找和创建是在运行时完成的。默认情况下,C ++静态链接(并且更快),Objective-C是动态的。
有趣的花絮
http://cocoasamurai.blogspot.com/2010/01/understanding-objective-c-runtime.html
[target doMethodWith:var1];被翻译成objc_msgSend(target,@ selector(doMethodWith :),var1);由编译器
http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/
显然,只需启动系统并启动一些应用程序,objc_msgSend()被称为数百万次。因此,每个循环都是重要的,毫无疑问,objc_msgSend()是用手工调整的程序集编写的。
objc_msgSend()旨在动态确定方法的实现 - 作为IMP绑定到目标实例上的选择器的IMP的函数指针 - 不改变任何调用者/被调用者状态。这启用了尾调用优化,允许objc_msgSend()直接将[JMP]跳转到方法的实现。这也是为什么你没有在backtraces中看到objc_msgSend()的原因,因为在objc_msgSend()中发生崩溃时这是