2014-05-16 4 views
7

[NSObject init]을 실행하면 런타임 오류가 발생하더라도 선택기 "init"을 사용하여 NSObject에서 respondSelector를 실행하는 이유는 무엇입니까? init은 인스턴스 메소드이므로 인스턴스가 아닌 클래스에서만 실행된다는 것을 알고 있습니다. 왜 이것이 런타임 오류를 반환합니까?[NSObject respondsToSelector : @selector (init)]가 1을 반환하는 이유는 무엇입니까?

if([NSObject respondsToSelector: @selector(init)] == YES) 
    [NSObject performSelector: @selector(init)]; 

respondsToSelector는 인스턴스 메소드이기 때문에 왜 처음부터 호출 할 수 있습니까?

답변

11

짧은 답변 :

  • 당신은 NSObject클래스, 또는 NSObject에서 상속하는 모든 클래스에 어떤 NSObject인스턴스 메소드 (예 : respondsToSelector: 또는 init)를 보낼 수 있습니다.
  • [NSObject init]은 CoreFoundation에서 재정의되고 런타임 예외가 발생합니다 ( OS X 10.6 이상에서 연결된 바이너리).

긴 답변 :

이의 당신의 마지막 질문부터 시작하자 : respondsToSelector 이후 또한

는, 인스턴스 방법입니다, 왜 먼저 그것을 호출 할 수도있다?

respondsToSelector:NSObject 클래스에 따르는 것으로 NSObject 프로토콜 인스턴스 메소드이다. 이제 NSObject 클래스 (및 각 하위 클래스) 은 루트 클래스 NSObject의 하위 클래스 인스턴스와 객체입니다.

이 설명 그렉 파커 문서 [objc explain]: Classes and metaclasses (강조 첨가)에 도시되어

더 중요한 메타 클래스의 슈퍼 클래스이다. 메타 클래스의 수퍼 클래스 체인은 클래스의 수퍼 클래스 체인 과 유사하므로 클래스 메소드는 인스턴스 메소드와 병렬로 상속됩니다. 루트 Metaclass의 수퍼 클래스가 루트 클래스 인 이므로 각 클래스 개체가 루트 클래스의 인스턴스 메서드에 응답합니다. 결국 클래스 개체는 다른 개체와 마찬가지로 루트 클래스의 하위 클래스의 인스턴스입니다.

이렇게하면 respondsToSelector:NSObject 클래스로 보낼 수있는 이유가 설명됩니다. 주어진 selector가있는 클래스 메서드가 있으면 반환 값은 YES입니다.

NSString *path = [NSString performSelector:@selector(pathWithComponents:) withObject:@[@"foo", @"bar"]]; 

performSelector:withObject:인스턴스 메소드 NSObject, 당신은 NSString클래스이 메시지를 보낼 수 있습니다 : 여기

는 또 다른 예이다. 이 경우, 결과는

NSString *path = [NSString pathWithComponents:@[@"foo", @"bar"]; 
최초의 질문에 이제

과 동일합니다 :

왜 "초기화하기"를 선택하여 NSObject의에 respondsToSelector를 실행 않아도 실행하지만 1 을 반환 [NSObject init]에 런타임 오류가 발생합니까? 위와 같은 추론

NSObject에서 파생되는 모든 클래스에 init 메시지를 송신 할 수 있어야한다.

// Replaced by CF (throws an NSException) 
+ (id)init { 
    return (id)self; 
} 

이 왜

[NSObject respondsToSelector:@selector(init)] == YES 

NSObject의의 의견을 설명합니다

지금 NSObject는 런타임에 예외를 던져 문서화 된 클래스 메소드 init, 이 http://opensource.apple.com/source/objc4/objc4-532.2/runtime/NSObject.mm 볼 수있다 .mm은 CoreFoundation에서 +init이 대체되고 실제로는이라고 명시합니다. 예외가 발생 될 때는 스택 백 트레이스는

(lldb) bt 
* thread #1: tid = 0x6eda, 0x01f69952 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT 
    .... 
    frame #8: 0x0156b9fc libobjc.A.dylib`objc_exception_throw + 323 
    frame #9: 0x01889931 CoreFoundation`+[NSObject(NSObject) init] + 241 
    * frame #10: 0x00002a51 foo`main(argc=1, argv=0xbfffee30) + 257 at main.m:25 

그래서이 방법 CoreFoundation에서 예외에 대한 책임이있다.


내가 모르는 init 방법은 CoreFoundation에서 (대신 NSObject에서 직접 예외를 throw)에서 재정의되고 대체 방법은 오픈 소스 저장소의 일부가 될 수없는 것 같습니다 이유http://opensource.apple.com/source/CF/CF-855.14/ (적어도 나는 그것을 거기에서 발견 할 수 없었다).

하지만 흥미로운 점은 OS 릴리스간에 관찰 된 동작이 변경되었을 수 있습니다.OS X 10.5에 컴파일

id x = [NSObject init]; 

경우, 그것은 작동하고 NSObject 클래스 객체를 반환합니다. 바이너리가 OS X 10.5에서 컴파일되고 링크되어 있고 같은 OS X 10.9와 같은 최신 릴리스에서 실행되는 경우에도 작동합니다.

또한 바이너리 OS X 10.6 이상에 연결되어있는 경우 (http://www.opensource.apple.com/source/CF/CF-476.14/CFPriv.h부터)

CoreFoundation`+[NSObject(NSObject) init]: 
0x1019d8ad0: pushq %rbp 
0x1019d8ad1: movq %rsp, %rbp 
0x1019d8ad4: pushq %rbx 
0x1019d8ad5: pushq %rax 
0x1019d8ad6: movq %rdi, %rbx 
0x1019d8ad9: movl $0x6, %edi 
0x1019d8ade: callq 0x1018dfcc0    ; _CFExecutableLinkedOnOrAfter 
0x1019d8ae3: testb %al, %al 
0x1019d8ae5: jne 0x1019d8af1    ; +[NSObject(NSObject) init] + 33 
0x1019d8ae7: movq %rbx, %rax 
0x1019d8aea: addq $0x8, %rsp 
0x1019d8aee: popq %rbx 
0x1019d8aef: popq %rbp 
0x1019d8af0: ret  
0x1019d8af1: movq %rbx, %rdi 
0x1019d8af4: callq 0x101a19cee    ; symbol stub for: class_getName 
0x1019d8af9: leaq 0x9fe80(%rip), %rcx  ; kCFAllocatorSystemDefault 
0x1019d8b00: movq (%rcx), %rdi 
0x1019d8b03: leaq 0xc5a66(%rip), %rdx  ; @"*** +[%s<%p> init]: cannot init a class object." 
... 
0x1019d8b72: callq *0x9e658(%rip)   ; (void *)0x00000001016b9fc0: objc_msgSend 
0x1019d8b78: movq %rax, %rdi 
0x1019d8b7b: callq 0x101a19d4e    ; symbol stub for: objc_exception_throw 

_CFExecutableLinkedOnOrAfter()의 어셈블리 코드에서 수표 보았다, 그 경우 예외 이 발생 될 수있다.

따라서 이전 OS 릴리스에서는 클래스 객체에서 init을 호출해야합니다. 따라서 과 CoreFoundation은 이전 버전과의 호환성을 위해 "이전 바이너리"에서 허용합니다.

+0

아주 좋은 답변입니다! –

1

respondsToSelector : 또한 NSProxy의 클래스 메서드이며 해당 메서드는 클래스를 수신기로 사용합니다. NSProxy 워드 프로세서,

respondsToSelector: 
Returns a Boolean value that indicates whether the receiving class responds to a given selector. 

+ (BOOL)respondsToSelector:(SEL)aSelector 
Parameters 
aSelector 
A selector. 
Return Value 
YES if the receiving class responds to aSelector messages, otherwise NO. 
1

에서 NSObject의의로 respondsToSelector 내부적으로 구현 : NSObject의 인스턴스가 언급 한 선택에 응답하면

+ (BOOL)respondsToSelector:(SEL)aSelector { 
    return class_respondsToSelector([self class], aSelector); 
} 

이 만 나타냅니다. 다양한 클래스가이 메서드를 재정 의하여 고유 한 구현을 갖습니다. 예를 들어 NSProxy는이 메서드를 재정 의하여 고유 한 구현을 제공합니다.

+0

하지만 그 클래스 메소드를 호출하는 것은 무엇입니까? NSObject 헤더를 보면 "- (BOOL) respondsToSelector : (SEL) aSelector;"가 표시됩니다. which is an instance method –

+0

클래스 메서드는 NSObject에 문서화되어 있지 않습니다. –

+0

respondsToSelector 클래스 메서드가 없으면 클래스에서 호출하려고하면 예외가 발생했습니다. 동일한 이름을 가진 문서화되지 않은 클래스 메서드가 있으므로 성공적으로 실행합니다. –

관련 문제