프로그래밍/Objective-C2009. 4. 11. 13:59
박진형 jenix@jinhyung.org

연세대학교 수학과를 휴학하고 넥스지(NexG)에서 병역 특례로 일하고 있다. 리눅스 커널 개발, 오브젝티브-C, 코코아(Cocoa) 애플리케이션 개발에 관심이 많으며 OSXDev(http://www.osxdev.org)의 공동 대표를 맡고 있다.


난이도 : 초급 
2007년 2월27일


[오픈 디벨로퍼웍스]는 여러분이 직접 필자로 참가하는 코너입니다. 이번에는 박진형님이 작성한, 그동안 잘 알려지지 않은 프로그래밍 언어 중 하나인 오브젝티브-C에 대해 소개합니다.

오브젝티브-C란

오브젝티브-C는 1980년대 중반부터 사용되어 온 오래된 언어지만 GNU C 컴파일러나 넥스트(NeXT) 시스템, 혹은 애플(Apple), 이 세 가지 중 어느 하나에라도 관심이 없던 독자라면 처음 들어보는 생소한 프로그래밍 언어일지도 모른다. 오브젝티브-C는 C에 스몰토크(Smalltalk)의 몇 가지 문법을 채택해 객체 지향 특성을 더한 C의 상위집합(superset)이다.

오브젝티브-C를 위한 런타임 라이브러리에는 현재 애플 맥 OS X에서 쓰이는 코코아 프레임워크와 GNU 프로젝트에서 만든 GNUstep, 이 두 가지가 있다. 코코아 프레임워크는 맥 OS X에서만 사용할 수 있고 GNUstep은 리눅스를 포함하여 유닉스 계열(맥 OS X도 포함) 운영체제와 MS 윈도우에서도 사용할 수 있다.

오브젝티브-C를 이용해 개발하려면 GNU C 컴파일러와 런타임 라이브러리인 맥 OS X의 코코아 또는 GNUstep 환경이 필요하다. GNUstep의 경우 대부분의 리눅스 배포본에서 패키지로 제공되며 MS 윈도우 사용자는 오브젝티브-C와 GNUstep을 모두 이용할 수 있는 바이너리 형태의 설치 프로그램을 사용하면 된다. 설치에 관한 자세한 내용은 GNUstep 홈페이지의 튜토리얼을 참고하면 된다.

오브젝티브-C는 비록 잘 알려지지 않았고 사실 C에 객체 구현을 위한 클래스 관련 키워드와 문법에 메시징 메커니즘만 추가한 것이 전부라고도 할 수 있지만 C에 이렇게 작은 변화를 준 것만으로 비교적 최근에 만들어진 스크립트 언어인 파이썬(Python)이나 루비(Ruby)만큼이나 재미있는 특징을 지니고 있다. 이제 그 특징들을 하나씩 함께 살펴보기로 한다.




위로


C에 추가된 문법 #1: 메시징

오브젝티브-C에 추가된 새로운 문법 하나는 객체에 메시지를 보내기 위한 것으로 다음과 같이 쓴다.

  

[anObject doSomethingWith: anotherObject];


이 문법은 스몰토크를 사용해 본 개발자라면 어느 정도 친숙하게 보일 것이다. 대괄호([])는 객체에 메시지를 보내는 것을 나타내는 표현이다. 즉 ‘anObject란 객체에 anotherObject란 객체와 함께 doSomethingWith이란 일을 시킨다’라는 의미다. 위 코드를 C++에서 함수를 호출하는 것으로 표현하면 다음과 같다.

  

anObject->doSomethingWith(anotherObject);


[]를 사용한 새로운 문법은 처음에는 매우 이상하게 보일지 모르지만 이 문법은 메시지(함수)의 인자가 많아질수록 코드의 의미를 더 명확하게 한다. 다음과 같은 예를 보자.

  

[NSCalendarDate dateWithYear:1965
month:12
day:7
hour:17
minute:25
second:0];


위 코드를 보면 각각의 인자가 무엇인지를 이해할 수 있다. 이를 C++에서는 다음과 같이 호출할 것이다.

  

dateObject->newDate(1965, 12, 7, 17, 25, 0);


물론 위와 같이 간단한 날짜 객체를 생성하기 위해 오브젝티브-C 코드는 C++에 비해 사용자가 입력해야 할 코드 분량이 늘어나는 단점도 있다. 하지만 여러 사람이 프로젝트를 진행하는 과정에서 다른 사람이 작성한 코드를 봐야 한다면 인자 설명을 위한 별다른 주석 없이 메시지만으로도 이 메시지가 무엇을 하는 코드인지 쉽게 알 수 있다는 것은 큰 장점이다.




위로


C에 추가된 문법 #2: 클래스

객체 지향 프로그래밍을 어느 정도 해본 독자라면 오브젝티브-C 문법을 몰라도 다음 코드가 하는 일을 바로 알아차렸을 것이다.

  

	myclass.h
	@interface MyClass : NSObject
	{
	  int aVariable;
	  id  subObject;
	}
	+ (id) alloc;
	+ (id) defaultObject;
	- (id) init;
	- (int) doSomethingWith: (id)anotherObject;
	@end

	myclass.m
	#import <myclass.h>
	@implementation
	- (id) init
	{
		... 객체에 대한 초기화 ...
	}
	- (int) doSomethingWith: (id)anotherObject
	{
		... 코드들 ...
		return 0;
	}
	@end


MyObject : NSObject는 MyObject가 NSObject를 상속한다는 의미다. NSObject는 GNUstep에서 모든 클래스의 최상위 클래스다. 물론 최상위 클래스를 새로 정의해 사용해도 되지만 이렇게 할 경우 GNUstep 런타임 라이브러리에서 제공하는 많은 기능을 사용할 수 없다. NSObject 외에도 배열 자료구조, 사전형 자료구조 등 수십 가지 자료구조를 GNUstep에서 제공한다.

C의 기존 키워드와 겹치지 않도록 클래스 선언과 클래스 메서드 선언을 위한 키워드가 따로 추가되었다. 클래스 정의는 @interface ... @end로 선언하고, 클래스 메서드 구현은 @implementation ... @end 사이에 구현한다. 클래스 메서드는 + 혹은 -로 시작하는데, +는 클래스 메서드(객체 인스턴스 없이 바로 클래스에서 사용 가능한 메서드)를 나타내고, -는 객체의 인스턴스 메서드를 가리킨다. 위에서 선언한 - (int) doSomethingWith: (id) anotherObject; 메서드는 앞에서 잠깐 설명했던 것처럼 다음과 같이 바로 사용할 수 있다.

  

int result = [myObject doSomethingWith: anotherObject];


또 다른 새로운 키워드는 #import인데 이는 기본적으로 #include와 용도가 같다. 단 C에서는 헤더 파일을 작성할 때 중복되어 인클루드 되는 것을 막기 위해 #ifdef HEADER_H #define HEADER_H ... #endif 매크로를 이용하는 데 비해 오브젝티브-C의 #import는 이를 하지 않더라도 헤더 파일을 한번만 인클루드 되도록 해주는 점에서 큰 차이가 난다.

한 가지 특이한 점은 소스코드 확장자로 m을 쓴다는 것이다. 또한 오브젝티브-C는 C++와도 섞어 사용할 수 있는데 이럴 경우엔 확장자를 mm으로 한다. GNU C 컴파일러는 확장자가 m이면 오브젝티브-C로, 확장자가 mm이면 오브젝티브-C++로 인식하여 컴파일을 한다. 필자는 C++ 코드를 포함하기 위해 오브젝티브-C++를 사용하기보단 다른 개발자들이 만들어둔 좋은 C++ 라이브러리들을 사용하기 위해 mm을 사용하는데 이렇게 하고 라이브러리를 링크만 하면 C++ 라이브러리를 오브젝티브-C에서 바로 사용할 수 있다. 오브젝티브-C로 개발을 새로 하게 되더라도 기존에 작성해 두었던 라이브러리들을 재사용하는 데 큰 문제가 없다. 멋지지 않은가?




위로


동적 바인딩

오브젝티브-C는 완전한 동적 바인딩(dynamic binding)을 지원한다(이는 순수 객체 지향 언어에 필요한 조건이다). 이는 메시지를 보내는 객체는 프로그램이 실제로 실행되기 전까지 어떤 클래스나 어떤 함수에도 제한되지 않는다는 의미다. 다시 말하면, 프로그래머는 프로그램을 실행해보기 전까지는 해당 객체가 특정 메시지에 대해 어떻게 반응할지 모른다는 것이다.

이러한 오브젝티브-C의 동적 바인딩은 GNUstep이나 애플의 코코아 프레임워크 같은 런타임 라이브러리에 의해 이루어진다. 위의 예에서 doSomethingWith:와 같은 메시지를 해당 객체에 보내면 런타임 라이브러리는 해당 객체의 정의를 살펴보고 메시지에 대응하는 메서드가 해당 객체에 있다면 해당 메서드를 호출한다.

기존 C 혹은 C++와 다르게 메시지를 일종의 테이블에서 찾아보고 호출하는 방식이 프로그램 속도를 느리게 하지 않을까 하는 의문이 생길 것이다. 하지만 메시지에서 메서드를 찾아내는 이러한 작업은 런타임 라이브러리에서 최적화되어 있기 때문에 그렇게 걱정할 일이 아니다. 속도가 특별하게 중시되는 경우라면 오브젝티브-C는 메시지에 해당하는 메서드를 미리 지정해 성능 문제가 없도록 할 수도 있다.

이러한 메시징 기법을 사용하기 때문에 오브젝티브-C에서 다형성은 C++와는 많이 다르다. 예를 들어 보자. 위의 MyClass에서 doSomethingWith: 메시지를 구현했고 다른 클래스인 YourClass에서 doSomethingWith: 메시지를 또 구현했다고 하자. 두 객체는 랜덤으로 생성되고 프로그램 로직에서 어떤 객체가 들어오는지 알 수 없다. 다음 코드를 보자.

  

// 이 코드는 오타가 아니다. C 함수에서 객체를 사용한 경우다.
	void someFunction(id randomObject)
	{
		[randomObject doSomethingWith:globalObject];
	}


위와 같이 행동하는 someFunction이 있다고 하면 randomObject에 doSomethingWith: 메시지를 보낸다. 그럼 randomObject가 무엇이든 원래 클래스에 구현되어 있는 doSomethingWith: 메시지대로 행동한다. 이것이 오브젝티브-C의 다형성이다. 기존 C++ 등에 익숙한 독자라면 뭔가 이상할 것이다. 저렇게 모든 객체를 가리킬 수 있는 id로 객체를 가져와 메시지를 보냈는데 만약 doSomethingWith:이란 메시지가 구현되어 있지 않다면 어떻게 될까? 오브젝티브-C에서는 이럴 경우 메시지를 해당 메시지를 처리할 수 있는 객체로 넘겨줄 수 있다.

  

    - (void) forwardInvocation: (NSInvocation*)anInvocation
    {
        if ([anObject respondsToSelector: [anInvocation selector]])
            [anInvocation invokeWithTarget: anObject];
        else
            [super forwardInvocation:anInvocation];
    }


anObject에 처리할 수 없는 메시지가 있는지 확인한다. 있으면 해당 객체로 메시지를 넘겨주고 아니면 해당 메시지를 상위 객체로 다시 전달한다. 바로 런타임에 해당 메시지가 있는지 확인하기 때문에 이러한 것이 가능하다.

오브젝티브-C의 이러한 특징은 클래스를 디자인할 때 일관성을 제공한다. 워드 프로세서 프로그램을 작성한다고 생각해 보자. 문서 안에는 텍스트가 있을 수 있고 그림 파일 또는 동영상 파일까지 첨부할 수도 있다. 복사하고 싶은 대상을 선택하고 메뉴의 복사 버튼을 누른다고 가정해보자. 이럴 경우 어떻게 할 것인가?

복사 메뉴를 누른 곳에서 선택된 아이템이 무엇인지 확인하고 행동하는 것이 맞는 것인가, 아니면 해당 객체를 어떻게 복사할지를 결정하는 것이 적절하겠는가? 각각의 텍스트 객체, 사진 객체, 동영상 객체에 copy: 메시지를 구현해 두면 워드 프로세서의 복사 메뉴에서는 id로 객체를 받고 [anObejct copy];라고 해당 객체에 copy: 메시지만 보내면 된다. 각각의 객체에서 copy: 메시지를 최종적으로 클립보드에 복사되게만 해두면 된다. 복사가 불가능한 객체라 copy: 메시지를 구현하지 않아 copy 메시지를 처리할 수 없는 경우 copy 메시지를 처리하는 객체를 하나 만들어 사용자 경고창으로 해당 객체를 복사할 수 없다는 경고문을 표시한다든가 하면 된다.




위로


손쉬운 확장

오브젝티브-C로 작성된 라이브러리들은 손쉽게 확장할 수 있다. 기존 클래스를 제작한 사람이 바이너리 파일만을 제공했을 경우, 해당 클래스에 새로운 기능을 추가하고 싶다면 일반적인 객체 지향 언어에서는 해당 클래스를 상속해 거기에 새로운 기능을 추가하려 할 것이다. 하지만 오브젝티브-C에서는 그렇게 하지 않는다. 다음 코드를 보자.

  

@interface MyClass (MyAdditionalMethods)
- (int) doSomethingWith: thisObject andThenWith: thatObject;
@end


@interface 클래스 이름 다음에 ()로 뭔가 하나 추가됐음을 볼 수 있다. 이를 오브젝티브-C에서는 카테고리라고 부른다. 이렇게 기존 MyClass에 자신만의 카테고리를 만들어 이 카테고리 안에 새로운 메서드를 추가할 수 있다. 이렇게 추가한 메서드는 MyClass 객체에서 모두 사용할 수 있다. 제한이 있다면 기존 클래스의 인스턴스 변수를 새로 추가하는 것은 불가능하다는 점이다. 이렇게 기존 클래스에 새로운 변수를 추가해야 할 경우는 새로운 객체를 디자인한다고 생각하고 C++와 마찬가지로 해당 클래스를 상속해 변수를 추가해야 한다.

기존 클래스의 메서드를 변경할 수도 있다. 기존 클래스에 있던 메서드와 동일한 메서드를 카테고리에 추가한다면 객체에 메시지를 보낼 때 카테고리에 있는 메서드를 우선적으로 찾아 사용하게 된다.

결론

지금까지 오브젝티브-C가 C와 다른 점 중에서 중요한 내용 몇 가지를 살펴보았다.

가장 큰 특징은 역시 오브젝티브-C는 동적 프로그래밍 언어라는 점이다. 스크립트 언어인 루비나 파이썬처럼 기본 자료형까지 동적은 아니지만(이는 C 언어의 상위집합이기 때문이다) 오브젝티브-C의 객체는 모두 실행시에 객체가 무엇인지 결정된다. 이는 프로그램을 디자인할 때 많은 이점을 제공한다.

또한 오브젝티브-C는 앞에서 언급했듯이 C의 상위집합이기 때문에 C를 사용하듯이 그대로 사용해도 된다(중간에 예제에서 봤듯이 C의 함수 선언, 호출, 구조체 사용 등 모든 것을 C로 생각해도 된다). 다만 기존 C에 없던 객체 지향 언어에 필요한 기능들이 추가된 것이기 때문에 기존에 C를 사용하던 사람이라면 쉽게 접근할 수 있을 것이다.

앞에서 소개한 것 이외에도 오브젝티브-C에는 생성한 객체의 메모리 관리, GUI 코드 작성을 쉽게 해주는 객체의 바인딩 등 런타임 라이브러리에서 제공하는 수많은 기능이 있다. 이러한 기능들에 익숙해지면 오브젝티브-C로 애플리케이션을 개발하는 데 있어서 생산성이 높아짐을 알 수 있을 것이다.

오브젝티브-C는 조만간 2.0 규격이 나올 예정이다. 보다 향상된 메모리 관리와 더 풍부해진 런타임 라이브러리를 지원한다고 한다. 20년간 꾸준히 사용되어 온 오브젝티브-C는 앞으로도 여러 개발자들이 꾸준히 이용할 것이고 발전할 것이다. 이번 기사를 통해 오브젝티브-C에 대한 모든 것을 소개할 수는 없었지만 앞으로도 꾸준히 발전할 오브젝티브-C로 즐겁게 개발할 수 있었으면 하는 바람이다. 

Posted by windship