Objective-C에서 nil에 메시지 보내기


107

Apple의 Objective-C 2.0 문서를 읽고있는 Java 개발자로서 " 메시지를 nil로 전송 "이 의미 하는 바가 무엇인지 궁금 합니다. 실제로 유용하다는 것은 말할 것도 없습니다. 문서에서 발췌 :

Cocoa에는이 사실을 활용하는 몇 가지 패턴이 있습니다. 메시지에서 nil로 반환 된 값도 유효 할 수 있습니다.

  • 메서드가 객체, 포인터 유형, sizeof (void *)보다 작거나 같은 크기의 정수 스칼라, float, double, long double 또는 long long을 반환하면 nil로 전송 된 메시지는 0을 반환합니다. .
  • 메서드가 Mac OS X ABI 함수 호출 가이드에 정의 된대로 레지스터에 반환되는 구조체를 반환하는 경우 nil로 전송 된 메시지는 데이터 구조의 모든 필드에 대해 0.0을 반환합니다. 다른 구조체 데이터 유형은 0으로 채워지지 않습니다.
  • 메서드가 앞서 언급 한 값 유형 이외의 것을 반환하는 경우 nil로 전송 된 메시지의 반환 값은 정의되지 않습니다.

Java가 위의 설명을 무시할 수 없도록 내 두뇌를 만들었습니까? 아니면 이것을 유리처럼 선명하게 만드는 내가 놓친 것이 있습니까?

Objective-C에서 메시지 / 수신자에 대한 아이디어를 얻었습니다. 수신자가 nil.


2
저는 또한 Java 배경 지식이 있었고 처음에는이 멋진 기능에 무서웠지만 이제는 정말 사랑 스럽습니다!;
Valentin Radu

1
감사합니다. 좋은 질문입니다. 그것의 이점을 보셨나요? 그것은 나를 "버그가 아니라 기능"이라고 생각합니다. Java가 예외로 나를 때리는 버그가 계속 발생하므로 문제가 어디에 있는지 알았습니다. 여기저기서 한두 줄의 사소한 코드를 저장하기 위해 널 포인터 예외를 교환하는 것이 행복하지 않습니다.
Maciej Trybiło

답변:


92

글쎄요, 매우 인위적인 예를 사용하여 설명 할 수 있다고 생각합니다. ArrayList의 모든 요소를 ​​출력하는 Java 메서드가 있다고 가정 해 보겠습니다.

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

이제 그 메서드를 다음과 같이 호출하면 : someObject.foo (NULL); 목록에 액세스하려고 할 때 NullPointerException이 발생합니다.이 경우 list.size (); 이제는 NULL 값을 사용하여 someObject.foo (NULL)를 호출하지 않을 것입니다. 그러나 someObject.foo (otherObject.getArrayList ());와 같은 ArrayList 생성 오류가 발생하면 NULL을 반환하는 메서드에서 ArrayList를 얻었을 수 있습니다.

물론 다음과 같은 작업을 수행하면 문제가 발생합니다.

ArrayList list = NULL;
list.size();

이제 Objective-C에는 동일한 방법이 있습니다.

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

이제 다음 코드가 있다면 :

[someObject foo:nil];

Java가 NullPointerException을 생성하는 것과 동일한 상황이 있습니다. nil 객체는 [anArray count]에서 먼저 액세스됩니다. 그러나 NullPointerException을 던지는 대신 Objective-C는 위의 규칙에 따라 단순히 0을 반환하므로 루프가 실행되지 않습니다. 그러나 설정된 횟수만큼 실행되도록 루프를 설정하면 먼저 [anArray objectAtIndex : i]에서 anArray에 메시지를 보냅니다. 이것은 또한 0을 반환하지만 objectAtIndex :는 포인터를 반환하고 0에 대한 포인터는 nil / NULL이므로 NSLog는 루프를 통해 매번 nil로 전달됩니다. (NSLog는 메소드가 아니라 함수이지만 nil NSString을 전달하면 (null) 출력합니다.

어떤 경우에는 NullPointerException이있는 것이 더 좋습니다. 프로그램에 문제가 있음을 즉시 알 수 있기 때문에 예외를 포착하지 않으면 프로그램이 충돌합니다. (C에서 이러한 방식으로 NULL을 역 참조하려고하면 프로그램이 충돌합니다.) Objective-C에서는 대신 잘못된 런타임 동작이 발생할 수 있습니다. 그러나 0 / nil / NULL / 0으로 된 구조체를 반환하는 경우 중단되지 않는 메서드가있는 경우 개체 또는 매개 변수가 nil인지 확인하지 않아도됩니다.


33
이 행동은 지난 20 년 동안 Objective-C 커뮤니티에서 많은 논쟁의 주제 였다는 점을 언급 할 가치가있을 것입니다. "안전성"과 "편의성"사이의 균형은 다른 사람들에 의해 다르게 평가됩니다.
Mark Bessey

3
실제로 메시지를 nil로 전달하는 것과 Objective-C가 작동하는 방식, 특히 ARC의 새로운 약한 포인터 기능 사이에는 많은 대칭이 있습니다. 약한 포인터는 자동으로 제로화됩니다. 따라서 0 / nil / NIL / NULL 등에 응답 할 수 있도록 API를 설계하십시오.
Cthutu

1
나는 당신이 경우에 있다고 생각 myObject->iVar그것은 C에 관계없이 경우, 충돌합니다 함께 또는 없이 객체. (
gravdig에

3
@ 11684 맞습니다.하지만 ->더 이상 Objective-C 작업이 아니라 일반적인 C-ism입니다.
bbum

1
최근 OSX 루트는 / 숨겨진 백도어 API를 이용 하기 때문에 OBJ-C의 무기 호 메시징의 모든 사용자 (단지 관리자)에 액세스 할 수 있습니다.
dcow apr

51

메시지에는 nil아무 것도 반환하지 않습니다 nil, Nil, NULL, 0, 또는 0.0.


41

다른 모든 게시물은 정확하지만 여기서 중요한 것은 개념 일 수 있습니다.

Objective-C 메서드 호출에서 선택기를 허용 할 수있는 모든 개체 참조는 해당 선택기에 대한 유효한 대상입니다.

이렇게하면 "대상 개체가 X 유형입니까?"라는 많은 비용이 절약됩니다. 코드-수신 객체가 선택자를 구현하는 한 어떤 클래스인지 전혀 차이가 없습니다 ! nil어떤 선택을 받아 그 NSObject라는 것입니다 - 그냥하지 않습니다 것을. 이렇게하면 "무를 확인하고 참이면 메시지를 보내지 마십시오"코드도 많이 제거됩니다. ( "만약 그것이 그것을 받아들이면 그것을 구현한다"개념은 또한 일종의 자바 인터페이스와 비슷한 프로토콜 을 생성 할 수있게 해준다 . 클래스가 명시된 메소드를 구현하면 프로토콜을 준수한다는 선언이다.)

그 이유는 컴파일러를 행복하게 유지하는 것 외에는 아무것도하지 않는 원숭이 코드를 제거하기 위해서입니다. 예, 한 번 더 메서드 호출의 오버 헤드가 발생하지만 프로그래머 시간 을 절약 할 수 있습니다. . 이는 CPU 시간보다 훨씬 더 비싼 리소스입니다. 또한 애플리케이션에서 더 많은 코드와 조건부 복잡성을 제거하고 있습니다.

비추천 사용자를위한 설명 : 이것이 좋은 방법이 아니라고 생각할 수 있지만 언어가 구현되는 방식 이며 Objective-C에서 권장되는 프로그래밍 관용구 입니다 (Stanford iPhone 프로그래밍 강의 참조).


17

의미하는 바는 objc_msgSend가 nil 포인터에서 호출 될 때 런타임이 오류를 생성하지 않는다는 것입니다. 대신 (종종 유용한) 값을 반환합니다. 부작용이있을 수있는 메시지는 아무것도하지 않습니다.

대부분의 기본값이 오류보다 더 적절하기 때문에 유용합니다. 예를 들면 :

[someNullNSArrayReference count] => 0

즉, nil은 빈 배열로 보입니다. nil NSView 참조를 숨기는 것은 아무것도하지 않습니다. 핸디, 응?


12

문서의 인용문에는 두 가지 개별 개념이 있습니다. 문서에서 더 명확하게 설명하면 더 좋을 수 있습니다.

Cocoa에는이 사실을 활용하는 몇 가지 패턴이 있습니다.

메시지에서 nil로 반환 된 값도 유효 할 수 있습니다.

전자가 여기서 더 관련이있을 수 있습니다. 일반적으로 nil코드를보다 간단하게 만들기 위해 메시지를 보낼 수 있습니다. 모든 곳에서 null 값을 확인할 필요가 없습니다. 표준 예제는 아마도 접근 자 메서드 일 것입니다.

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

메시지를 보내는 경우 nil유효하지 않았다,이 방법은 더 복잡 할 것이다 - 당신은 보장하기 위해 두 개의 추가 검사를해야 할 것 value하고 newValue없는 nil그들에게 메시지를 보내기 전에.

그러나 후자의 지점 (메시지에서 반환 된 값 nil도 일반적으로 유효 함)은 전자에 승수 효과를 추가합니다. 예를 들면 :

if ([myArray count] > 0) {
    // do something...
}

이 코드는 다시 한 번 확인할 필요가 없습니다. nil 값을 자연스럽게 흐릅니다.

이 모든 것이 말하면, 메시지를 보낼 수있는 추가적인 유연성은 nil비용이 듭니다. 값이 nil. 일 수있는 가능성을 고려하지 않았기 때문에 특정 단계에서 특이한 방식으로 실패하는 코드를 작성할 가능성이 있습니다 .


12

에서 그렉 파커사이트 :

LLVM 컴파일러 3.0 (Xcode 4.2) 이상을 실행하는 경우

반환 유형으로 nil에 대한 메시지 | 반환
최대 64 비트의 정수 | 0
long double까지의 부동 소수점 | 0.0
포인터 | 무
구조체 | {0}
모든 _ 복합 유형 | {0, 0}

9

이는 안전을 위해 모든 곳에서 nil 객체를 확인할 필요가 없다는 것을 의미합니다. 특히 :

[someVariable release];

또는 언급했듯이 다양한 count 및 length 메서드는 nil 값이있을 때 모두 0을 반환하므로 nil에 대한 추가 검사를 추가 할 필요가 없습니다.

if ( [myString length] > 0 )

아니면 이거:

return [myArray count]; // say for number of rows in a table

동전의 다른 쪽은 "if ([myString length] == 1)"와 같은 버그의 가능성이 있다는 점을 명심하십시오
hatfinch

버그는 어때? [myString length]는 myString이 nil이면 0 (nil)을 반환합니다. 문제가 될 수있는 한 가지는 myView가 nil이면 이상한 것을 줄 수있는 [myView frame]입니다.
Kendall Helmstetter Gelner 2010

기본값 (0, nil, NO)이 "유용하지 않음"을 의미한다는 개념을 중심으로 클래스와 메소드를 설계한다면 이것은 강력한 도구입니다. 길이를 확인하기 전에 문자열에서 nil을 확인할 필요가 없습니다. 나에게 빈 문자열은 텍스트를 처리 할 때 쓸모없고 nil 문자열입니다. 저는 또한 Java 개발자이고 Java 순수 주의자들이 이것을 피할 것이라는 것을 알고 있지만 많은 코딩을 절약합니다.
Jason Fuerstenberg 2012

6

"수신자가 nil"이라고 생각하지 마십시오. 나는이 동의 입니다 꽤 이상한. nil에 메시지를 보내는 경우 수신자가 없습니다. 당신은 아무것도 메시지를 보내지 않습니다.

이를 처리하는 방법은 Java와 Objective-C의 철학적 차이입니다. Java에서는 오류입니다. Objective-C에서는 작동하지 않습니다.


Java에서 해당 동작에 대한 예외가 있습니다. null에서 정적 함수를 호출하면 변수의 컴파일 타임 클래스에서 함수를 호출하는 것과 같습니다 (null인지 여부는 중요하지 않음).
Roman A. Taycher

6

nil로 전송되고 반환 값의 크기가 sizeof (void *)보다 큰 ObjC 메시지는 PowerPC 프로세서에서 정의되지 않은 값을 생성합니다. 또한 이러한 메시지는 Intel 프로세서에서 크기가 8 바이트보다 큰 구조체 필드에서도 정의되지 않은 값이 반환되도록합니다. Vincent Gable은 자신의 블로그 게시물 에서이를 잘 설명 했습니다.


6

다른 답변 중 어느 것도 이것을 명확하게 언급하지 않았다고 생각합니다. Java에 익숙하다면 Mac OS X의 Objective-C에는 예외 처리 지원이 있지만 선택 가능한 언어 기능이라는 점을 명심해야합니다. 컴파일러 플래그로 켜거나 끕니다. 내 생각에 "메시지를 보내는 nil것은 안전하다" 라는이 디자인은 언어에 예외 처리 지원을 포함하기 이전에 비슷한 목표를 염두에두고 수행 된 것 같습니다. 메서드 nil는 오류를 나타 내기 위해 반환 될 수 있으며 메시지를nil 일반적으로 반환nil그러면 오류 표시가 코드를 통해 전파 될 수 있으므로 모든 메시지에서이를 확인할 필요가 없습니다. 중요한 지점에서만 확인하면됩니다. 개인적으로 예외 전파 및 처리가이 목표를 해결하는 더 좋은 방법이라고 생각하지만 모든 사람이 이에 동의하는 것은 아닙니다. (반면에 저는 메소드가 던질 수있는 예외를 선언해야한다는 Java의 요구 사항이 마음에 들지 않습니다. 이는 종종 코드 전체에 걸쳐 예외 선언 을 구문 적으로 전파 하도록 강요합니다 . 그러나 이것은 또 다른 논의입니다.)

나는 비슷하지만 더 긴 관련 질문에 대한 답변을 게시했습니다 . "Objective C에서 모든 객체 생성이 성공했다고 주장합니까?" 더 자세한 정보를 원하시면.


그런 식으로 생각한 적이 없습니다. 이것은 매우 편리한 기능인 것 같습니다.
mk12

2
좋은 추측이지만, 결정이 내려진 이유에 대해 역사적으로 부정확합니다. 예외 처리는 처음부터 언어에 존재했지만 원래 예외 처리기는 현대 관용구에 비해 매우 원시적이었습니다. Nil-eats-message는 Smalltalk에서 Nil 객체 의 선택적 동작 에서 파생 된 의식적인 디자인 선택이었습니다 . 원래 NeXTSTEP API가 설계되었을 때 메서드 체인은 매우 일반적이었으며 nil체인을 NO-op으로 단락시키는 데 자주 사용되었습니다.
bbum 2013

2

C는 프리미티브 값의 경우 0으로, 포인터의 경우 NULL (포인터 컨텍스트에서 0과 동일)을 나타냅니다.

Objective-C는 nil을 추가하여 C의 아무것도 표시하지 않습니다. nil은 아무것도에 대한 객체 포인터입니다. 의미 상 NULL과 구별되지만 기술적으로는 서로 동일합니다.

새로 할당 된 NSObjects는 내용이 0으로 설정된 상태에서 생명을 시작합니다. 이것은 객체가 다른 객체에 대해 갖는 모든 포인터가 nil로 시작한다는 것을 의미하므로, 예를 들어 init 메소드에서 self. (association) = nil을 설정할 필요가 없습니다.

그러나 nil의 가장 주목할만한 동작은 메시지를 보낼 수 있다는 것입니다.

C ++ (또는 Java)와 같은 다른 언어에서는 프로그램이 중단되지만 Objective-C에서는 nil에 대한 메서드를 호출하면 0 값이 반환됩니다. 이것은 어떤 일을하기 전에 nil을 검사 할 필요가 없기 때문에 표현식을 크게 단순화합니다.

// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

Objective-C에서 nil이 어떻게 작동하는지 알고 있으면 이러한 편리함이 응용 프로그램에 숨어있는 버그가 아닌 기능이 될 수 있습니다. nil 값이 원치 않는 경우를 방지해야합니다. 확인하고 조기에 반환하여 자동으로 실패하거나 NSParameterAssert를 추가하여 예외를 throw합니다.

출처 : http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (nil에게 메시지 보내기).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.