티스토리 뷰

아름답고 자연스러운 오브젝티브-C

필자가 오브젝티브-C(Objective-C)를 접한 것은 올해 초, 갑갑한 문법적인 표현(Syntax)을 보니 코드를 보기가 싫어졌었다. 하지만 많은 iOS 개발자가 생겨나고 맥킨토시(Macintosh)를 쓰면서 자연스럽게 맥용 응용 프로그램에 관심이 생기기 시작했다. 처음에는 리눅스와 대부분의 운영체제를 지원하는 Qt(큐티) 프레임워크를 봐오다가, 코코아(Cocoa) 를 알게 되면서 맥킨토시에 가장 아름다운 UI 프레임워크인 것을 느끼게 되었다고 할까.

오브젝티브-C는 매우 깊은 역사가 있다. 이 역사에 대해서는 다음의 위키피디아(Wikipedia) 를 참고하기 바란다. 필자도 이 언어에 대한 깊은 역사를 이렇다 할 만큼 자신 있게 설명해 주기 힘들 것 같다.

.. 생략 .. [1]
오브젝티브-C는 스텝스톤(Stepstone)이라는 회사에서 일하고 있던 브래드 콕스(Brad Cox)와 톰 러브(Tom Love)라는 두 연구원이 만들었다. 역시 1980년대 초의 일이다. 이 두 사람은 1981년 ITT의 프로그래밍 기술 센터(Programming Technology Center)에서 일하던 시절 스몰토크를 처음 접했다. 콕스는 소프트웨어 설계와 프로그래밍에 있어서 재사용성의 문제에 흥미를 느끼게 되었고, 스몰토크같은 프로그래밍 언어가 ITT 시스템 개발자들이 개발을 진행할 강력한 환경을 만드는 데 필요불가결한 요소임을 깨닫게 되었다. 콕스는 C 컴파일러를 고쳐 스몰토크의 기능 일부를 추가하기 시작했고, 곧 그 스스로 OOPC라고 불렀던 C의 객체지향 버전을 내놓게 되었다. 한편 러브는 1982년에 Schlumberger Research에 채용되었고, Smalltalk-80의 최초 상업적 버전을 써 볼 기회를 가지게 되었다. Smalltalk-80은 이후 그들이 낳은 정신적 자식의 성장에 큰 영향을 끼쳤다.
.. 생략 ..

필자가 오브젝티브-C를 접하면서 ‘빙고!’를 외쳤다. 현대적인 언어의 대부분이 C언어, 그리고 바로 이 오브젝티브-C 언어의 영감을 받았다고 해도 무리는 아니다. (단, 현대적인 언어의 정의는 스스로 정의를 내리기 바란다.)

그렇다면 왜 오브젝티브-C 언어가 아름다운지 보자.

아름다움을 만들어 준 메시지 기반 언어

오브젝티브-C의 가장 기초적인 문법을 일반적인 객체 지향 언어의 문법과 비교해 보자.

// 일반적인 객체지향 언어들의 표현
void* obj = data->get_data(); // 또는
void* obj = data.get_data(); // 또는
Object obj = data.get_data();

// 오브젝티브-C 언어
NSObject* obj = [data get_data];

위의 일반적인 객체지향 언어와 오브젝티브-C 언어는 표현하는 방법에 있어 다른 점이 보인다. 자, 무엇이 다른지 한번 살펴보자.

  1. 문법 표현 방법 : 가장 차이가 나는 점은 식의 표현을 대괄호([ ])로 묶여 있다. (메서드 호출 시)
  2. 메서드 호출 방법 : 포인터(->) 또는 참조(.)를 통해 호출하지 않고 공백(a space)으로 메서드를 가리켜 호출한다.

위 코드가 컴파일 된다면 더욱 명확하게 차이점을 볼 수 있다.

일반적인 객체지향 언어의 경우 컴파일 된 어셈블리(기계어) 코드는 메서드가 메모리 상에 상주하고 있는 주소를 통해 직접 메모리를 엑세스하여 메서드를 실행한다.
오브젝티브-C의 경우 바로 이것이 오브젝티브-C 언어의 특징인 메시지를 통해 메서드를 호출하는 방법이다. data 객체는 get_data() 메서드를 직접 호출하지 않고 다른 대리자(위임)를 통해 메서드를 실행한다.

그렇다면 오브젝티브-C는 대리자를 통해 메서드의 실행을 위임함으로서 장점은 뭘까?

  1. NullPoint 예외(오브젝티브-C의 nil 또는 NULL)나 기타 치명적인 오류를 언어적인 레벨에서 차단한다.
  2. 관리언어(Managed Languages)인 자바, C# 등에서 즐겨 쓰는 Code Interception 등의 기법과 유사한 것들을 쉽게 확장할 수 있는 포인트를 제공한다.
  3. LLVM과 같은 가상 머신(Virtual Machine) 매커니즘을 손쉽게 적용할 수 있다. (clang)
딱히 바로 생각나는 것은 몇 안되지만, 더 많은 장점이 있음은 분명하다. 그리고 오브젝티브-C의 메시지 기반으로 작동하는 언어적인 특성을 분석해보자

오브젝티브-C 메시지 기반 메서드 호출

1. objc_msgSend 함수의 메커니즘

오브젝티브-C는 objc_msgSend() 함수를 이용하여 메시지를 보낸다. 이것은 함수만 해당되는 사항은 아니다. 위에서 살펴본 것과 같이 식의 표현을 대괄호([ ... ]) 안에 코드로 표현하는데 이 대괄호 안에 들어가는 모든 객체건 뭐건 간에 메시지를 통해 값을 가져오거나 메서드를 호출하게 된다.

아래 보이는 어셈블리 코드는 필자가 오브젝티브-C로 작성한 맥용 데스크탑 응용 프로그램 중 일부를 어셈블리 레벨에서 디버깅 하는 스크린샷이다. 다음에 사용된 코드의 일부는 다음과 같다.

[self.sidebar setTarget:self];

sidebar는 인스턴스화 된 객체(Object)이다. 오브젝티브-C는 메서드 호출에서만 메시지를 보내는 것이 아니라는 것이다. 그리고 objc_retainAutoreleasedReturnValue() 함수는 매우 긴 이름이지만, 맨 앞 글자의 retain만 주목해서 보면 된다. sidebar 인스턴스는 현재 컨텍스트(Context)에서 참조하므로 참조 카운터(References Count)를 1 증가 시킨다.


2. objc_msgSend 함수 내부에서 전달받은 메시지

아래의 스크린샷은 objc_msgSend() 함수의 내부에서 RIP 레지스터가 가리키는 메모리 주소의 HEX 이다. '73 69 64 65 62 61 72 00'(sidebar) 문자열은 objc_msgSend() 함수에 의해 RTTI(Run-time Type Information) 정보로 객체를 가져오기 위한 메시지라고 보면 된다. lldb의 're r' 명령을 통해 레지스터의 값들을 확인할 수 있다.

얼핏 보아도 아래의 스크린샷의 메모리 공간은 인스턴스 객체의 메시지와 메서드의 메시지들을 몰아서 외부에서 참조할 수 있도록 나열한 것임을 알 수 있다.


3. objc_msgSend() 메서드의 시그너처가 선언된 곳에서 objc_msgSend 호출

lldb 디버거를 통해 ni 명령으로 objc_msgSend() 함수에 진입하면 메시지 전달 함수가 메모리 상에 상주해 있는 곳으로 이동 시킨다.


4. 컨텍스트(Context)에서 전달된 메시지를 순회하여 검색/실행

전달되는 메시지를 통해 인스턴스의 메타데이터를 사용하여 실제로 객체를 가져오게 된다. 아래의 네모난 상자의 명령들이 바로 객체를 찾아오기 위한 루프(Loop)이다. 


5. 메시지에 의해 처리된 객체는 일부 캐싱하여 성능 향상을 꾀함

재미있는 점은 매번 메시지의 정보를 순회하면서 찾지 않고, 한번 찾은 정보는 별도로 캐싱 하고 있는 공간에서 검색하게 된다.


지금까지는 sidebar 인스턴스를 가져오기 위한 과정이었다. 이제 이 인스턴스 객체의 메서드를 호출하는 과정인데, objc_sendMsg()를 통해 메시지를 전달하여 찾은 메서드를 실행하게 된다.



댓글