[Objective-C] nil 제대로 알자

Objective-C의 nil 과 NULL 이라는 상수는 기술적으로 혼용가능하다. 그러나 일반적으로 nil 은 객체 참조에 사용되고, NULL 은 그 밖의 다른 포인터 자료형에 사용된다. (예를 들면  MyClass * myObject = nil,  int *iPtr = NULL )

자바에서는 객체 참조값이 null이면 보통 NullPointerException 이 발생하고 프로그램이 종료된다. 따라서 자바  프로그램밍에서는 참조값이 null 인지 확인하는 구문이 자주 쓰인다.

그러나 Objective-C 의 메시지 전송은 자바의 그것과 다르다. 이전 포스팅(Objective-C 메시지 전송)에서 정리한 내용처럼, Objective-C에서는 메시지 전송 과정에서 중앙 메시지 전송 함수가 수신인의 포인터 값을 확인하는데, 이 때 수신자 포인터가 nil 값이면 메시지 전송 함수는 즉시 반환된다.

즉, 메시지 수신 객체가 nil 값이라도 Exception이 발생하지 않고, 아무런 작업 없이 바로 반환된다. 리턴값이 있는 경우에는 0 이나 nil 또는 NO 값을 반환한다.

따라서 자바와 달리 Objective-C에서는 메시지를 보내기 전에 메시지 수신자 값의 nil 여부를 확인하는 것은 불필요하다. nil 확인작업이 이미 내부적으로 진행되기 때문이며 그렇기 때문에 코드는 간결해 질 수 있다.

다음과 같이 무엇인가를 그리는 구문을 생각해보자.

-(void)drawRect:(NSRect)dirtyRect {

    ...
    MyMap *map = [self myMap];
    [map drawRect:dirtyRect];
    [[map customLocationColor] setStroke];
    [[map locationHighlightPath] stroke];

}

만약 map 객체가 nil 이면 그 아래 메시지는 모두 전달되지 않는다. 반면 map 객체는 nil 이 아니지만 -customLocationColor 메소드의 결과가 nil 이면 -setStroke 메시지가 호출되지 않는다. 마찬가지로 map 객체값이 nil 아니고-locationHighlightPath 메소드 반환값이 nil 이면 -stroke 메시지는 전달되지 않는다.

nil 객체에 메시지를 전달해도 아무런 Exception이 발생하지는 않지만 메시지에 전달되는 인자는 정상적으로 진행된다. 즉, 인자에서 메시지를 전달하거나 증가연산자(++) 가 사용되는 경우에는 비록 메시지 수신 객체가 nil 이라 하더라도 인자값은 정상적으로 처리되므로 주의해야 한다.

nil 수신자에 메시지를 전송할 경우 반환되는 값은 아래와 같다.

반환값으로 구조체를 넘겨받는 경우에는 앞에서 설명한 nil 수신자 처리가 정상적으로 동작하지 않는다. 따라서 이 경우에는 자바에서처럼 메시지를 전송하기 전에 수신자의 nil 여부를 확인해야 한다.  CGRect 등의 구조체는 어플리케이션에서 많이 사용되므로 주의할 필요가 있다.  (구조체를 반환하는 메시지 전송의 경우, 구조체가 하드웨어 레지스터를 통해 반환되는 경우는 예외라고 한다. 이에 대한 확인은  ABI Function Calling Guide 를 통해 확인가능하다고 하는데 구조체 반환 메시지에서는 수신자 nil 참조 확인을 무조건 하는 것으로 기억하는 것이 편할 것 같다.)

위와 같은 nil의 특성을 고려해 프로그램을 구성한다면 다음의 세 가지의 원칙을 지키는 것이 바람직하다.

1) 프로퍼티 접근자 생성

프로퍼티의 접근 메소드를 만들어 사용하면 프로퍼티 소유 객체의 값이 nil 인 경우에도  [myObject myProperty] 와 같이 사용할 수 있다. (프로퍼티 접근자 생성은 애플에서 권장하는 방법이다.)

그러나 만약 프로퍼티의 접근 메소드를 만들지 않고, myObject -> myProperty 로 코드를 작성할 경우엔 프로퍼티 소유 객체 myObject 가 nil 이면 프로그램은 종료된다.

2) 부재중 동작 패턴

객체가 부재중, 즉 nil 일 때 상황에 따라 추가적인 동작을 지정해야 하는 경우가 있다.

예를 들어 다음과 같다.


...
NSLock *lock;
...
-(void)method_O1{
    [lock lock];
    // do something
    [lock unlock];
}

-(void)method_02{
    [lock lock];
    // do something
    [lock unlock];
}

-(void)makeThreadSafe{

    lock = [NSLock new];
    ...
}

처음에는 lock 변수는 nil로 설정되어 있기 때문에, [lock lock] , [lock unlock] 은 아무런 작업을 하지 않는다. 실제로 스레드 안정성을 확보할 필요가 없을 때 이는 의미있게 사용될 수 있다.

그러나 다중 스레드로 동작하는 환경에서 스레드 안정성을 확보해야 하는 상황이 생길 수 있다. 이 때 위와 같은 방식으로 프로그래밍을 했다면 -makeThreadSafe 메소드를 호출해 lock 변수에 객체를 만들어 넣으면 된다.

위 예시처럼, 객체가 부재중(nil)일 때와 그렇지 않을 때의 용도를 고려해서 프로그래밍을 해야 할 필요도 있다. 다음과 같은 상황을 참고하자.

  • 로그 객체 : 로그 출력 객체가 설정돼 있다면 전달받은 메시지를 로그에 출력하고, 로그 객체가 없다면 로그 관련 작업을 하지 않는다.
  • 리스너 객체 : 리스너가 설정된 상태에서만 변경된 정보에 대한 메시지가 전송된다. 이미 리스너가 설정된 경우, nil 로 재설정하면 더 이상 변경사항이 전달되지 않는다.
  • 위임 객체 : 위임 객체가 있다면 위임 객체의 기능을 호출하고 없다면 기본 기능을 사용한다.

3) 없음의 일관성

프로퍼티에서는 0 또는 nil 의 개념을 없는 객체의 개념과 일관되게 설계하는 것이 좋다. 즉, 프로퍼티가 부정적인 값보다 긍정적인 값을 표현하도록 프로퍼티를 정의하는 것이 좋은 습관이다.

예를 들어 -(BOOL)isEmpty 보다 -(BOOL)hasObjects 가 바람직하다. isEmpty를 사용하면 수신자가 nil 일때 NO를 반환하므로 수신자가 nil 이란 의미인지, 객체를 가지고 있다는 의미인지 알 수 없다.

nil 을 잘 활용하기 위해서는 문서를 확인해야 한다. 자바와는 다르게 코코아 컬렉션 클래스는 nil 객체를 값이나 키로 지정할 수 없게 제한한다. 따라서 nil 값이 컬렉션에 추가되지 않도록 주의가 필요하다.

*Reference : Learn Objective-C for Java Developers

Advertisements
Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: