为什么我的类只在Debug版本中响应我的全局SEL变量?

时间:2013-05-06 20:45:41

标签: xcode clang objective-c++ clang++

我正在使用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,它可以正常工作。

我是否遗漏了某些内容或依赖未定义的行为?

2 个答案:

答案 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()中发生崩溃时这是