메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Objective-C 언어

한빛미디어

|

2002-11-07

|

by HANBIT

11,337

저자: 마이크 빔(Mike Beam), 역 한빛리포터 왕수용

지난번 기사에서 우리는 객체지향 언어의 중요 개념에 대해서 알아 보았다. 이번에는 Objective-C가 이러한 개념들을 어떻게 구현해 놓았는지, 실제로 Objective-C 언어가 어떻게 생겼는지에 대해 살펴볼 것이다.

객체 지향 언어에서 가장 중요한 요소는 바로 메시지를 보내는 기능이다. Objective-C에서 객체에 메시지를 보내려면 다음과 같이 쓴다.
[receiver message]
receiver는 메시지를 받을 객체이고, message는 메시지를 받은 receiver 객체가 실행해야 할 메소드이다. 예를들어 프로그램에 dataTable이라는 객체가 있다고 가정하자. 그리고 이 객체의 데이터를 업데이트 하려고 한다. 그러기 위해서는 다음과 같이 메시지를 보내면 된다.
[dataTable reloadData];
Objective-C에서 인자(argument)는 콜론(:)을 쓰고 그 다음에 쓰면 된다.
[textField setEditable:YES];
인자는 콜론만 붙이면 얼마든지 많이 쓸 수 있다. 메시지는 반환(return) 형식(type)과 메시지가 받는 인자의 형식(type)만 맞으면 다른 메시지 안에 포함될 수도 있다. 예를 들면 슬라이더로부터 값을 읽어서 텍스트 필드에 표시되게 할 수 있다.
[textField setDoubleValue:[slider doubleValue]];
여기에서 볼 수 있듯이 [slider doubleValue][textField setDoubleValue: ]의 인자로 사용되었다. doubleValue 명령은 double 형식의 값을 반환하고, setDoubleValue는 double형식의 값을 인자로 받는다.

데이터 형식에 대해 이야기 하자면, Objective-C에서 객체는 id라는 데이터 형식을 갖는다. id 형식의 변수는 객체를 위한 식별자(identifier)이다. 실질적으로는 단지 객체의 데이터 구조에 대한 포인터 형식으로 되어 있다. 더 자세한 내용은 본 기사의 범위를 벗어나는것 같다. 실제 코드상에서 객체를 지칭하는 변수는 다른 변수를 선언하는 것과 마찬가지로 다음과 같이 선언할 수 있다.
id anObject;
Objective-C에서는 기본 반환 값은 id 형식의 값이다. 그래서 메소드 반환 값의 데이터 형식이 지정되어 있지 않을 경우에는 자동적으로 id 형식으로 지정된다.

데이터 변수를 특정한 클래스 형식으로 지정하는 것도 가능하다. 이것을 정적 형식(static typing)이라고 한다. 객체를 지칭하는 모든 변수는 메모리상의 위치에 대한 포인터 값이다. 이것에 대한 구현은 대부분 실제 프로그래머에게는 잘 드러나지 않는다. 하지만 정적 형식을 이용할 때에는 포인터라는 것이 명확히 드러난다.

id 형식의 변수를 생성할 때에는, 객체에 대한 포인터라는 사실이 정확히 나타나지 않는다. id는 실제로 그 정의는 (객체 식별자로서) 포인터 형식이다. 그러나 만일 스트링을 지칭하는 변수가 있을 경우에는 그것을 정적으로 NSString 형식으로 지정할 경우에는 그 변수가 포인터라는 사실을 코드에 기술해야만 한다. 이때에는 C 포인터 선언 문법을 그대로 사용한다.
NSString *aString;
변수이름 앞에 *표시는 변수 이름에 포함되지 않고, 단지 이것은 aString변수가 NSString형식에 대한 포인터라는 것을 나타낸다. 사실 객체 식별자 변수가 실제로 포인터라는 사실을 알 수 있는 부분은 선언할 때 뿐이다. 선언한 이후 aString 변수를 사용할 때에는 다른 변수와 똑같이 사용한다.

만일 포인터 개념에 대한 이해가 조금 어렵다면, Brian Kerrnighan와 Dennis Ritchie의 『The C Programming Language』라는 책을 보면 도움이 될 것이다. 또한 Steve Oulline의 『Practical C Programming』도 추천하고 싶다.

겹처서 메소드를 사용하는 또다른 방법

객체에 대한 데이터 형식이 있기 때문에, 또다른 방식으로 메소드를 겹쳐서 호출할 수 있다. 즉 id 형식을 반환하는 메시지는 또다른 메시지를 받는 객체가 될 수 있다.
double number;
number = [[anArray lastObject] doubleValue];
물론, 여기서는 [anArray lastObject]에서 반환되는 객체가 doubleValue 메시지를 받을 수 있다고 가정한 것이다. 위 코드는 배열이 있다고 했을 때 [anArray lastObject] 메시지는 배열 내에서 마지막 객체를 반환한다. 그리고 그 객체에 doubleValue 메시지를 보낸다. 또다른 방법으로 아래와 같이 길게 코드를 작성할 수도 있다.
id tempObject;
double number;
tempObject = [anArray lastObject];
number = [tempObject doubleValue];
Objective-C에서 새로운 클래스 선언하기

이제 소스코드에서 어떻게 클래스를 선언하는지 알아보자. 인터페이스와 구현을 분리해야 하는 기본 정신에 입각하여 Objective-C에서는 새로운 클래스를 2개의 파일에 선언한다. 하나는 인터페이스 파일이고 또 하나는 구현(implementation) 파일이다.

인터페이스에서는 모든 인스턴스 변수 선언과 모든 메소드 선언, 즉, 클래스를 사용하기 위한 모든 정보가 들어 있다. 만약 프로그래머가 그 클래스를 사용하면서 그 클래스가 어떤 일을 할 수 있는지 알고 싶을 때에는 인터페이스 파일만 보면 된다. 구현 파일은 실제로 메소드들이 구현된 소스 코드가 들어가 있다.

클래스의 인터페이스와 구현은 일반적으로 2개의 다른 파일로 분리되어 있다. 2개로 분리하지 않는다고 해서 컴파일 할 수 없는 것은 아니다. 구현 파일의 확장자는 Objective-C 소스파일을 뜻하는 .m이다. 인터페이스 파일은 일반적인 C의 헤더파일을 뜻하는 .h이다. 클래스 파일 이름은 보통은 실제 그 클래스 이름과 동일하게 한다. 물론 이것을 다르게 한다 해도 컴파일은 할 수 있다. 예를 들어 "Circle"이란 클래스가 있다면 이것은 보통은 Circle.hCircle.m 파일에 구현되어 진다.

인터페이스 파일

인터페이스 파일은 클래스를 구성하는 인스턴스 변수와 메소드의 선언으로 이루어져 있다. 인터페이스 파일의 구성은 아래와 같다.
@interface ClassName : ItsSuperclass
{
instance variable declerations
}
method declerations
@end
인터페이스 선언은 항상 @interface 컴파일러 지시자로 시작하고, @end 컴파일러 지시자로 끝난다. @interface 지시자 다음에는 클래스 이름이 따라온다. 클래스 이름 다음에는 콜론을 쓰고 그 다음에는 새로운 클래스가 상속하는 상위 클래스의 이름을 쓴다. 만일 상위 클래스를 지정하지 않으면 코코아의 NSObject 와 같은 완전히 새로운 상위 클래스를 만든다는 뜻이다. († 역자 주: 실제로 이런 경우는 거의 없다.)

중괄호를 연 다음 인스턴스 변수 선언이 그 다음에 따라온다. 이것이 바로 클래스의 메소드들이 사용해야 할 클래스의 데이터 구조를 이루게 된다. 예를 들어 Circle 클래스에서는 다음과 같은 변수 선언을 할 수 있을 것이다.
double radius;
double xLocation;
double yLocation;
NSColor *color;
인스턴스 변수 선언은 항상 데이터 형식을 쓰고 변수 이름을 쓰는 방식으로 한다. 나중에 자세히 언급할 NSColor는 Appkit에 선언되어 있는 컬러를 표현하는 클래스이다.

인스턴스 변수 선언 다음에는 메소드 선언이 나온다. C함수와 같이 Objective-C에서 메소드는 반환 형식과 인자를 가진다. 그에 더해서 메소드는 클래스 메소드이던지 인스턴스 메소드 둘 중에 하나이다. 클래스 메소드는 클래스 객체에서 이용되는 것이고, 인스턴스 메소드는 클래스의 한 객체에서 이용되는 것이다. 메소드 이름 앞에 +표시를 붙이는 것은 이것이 클래스 메소드라는 뜻이다.
+ alloc
그리고 -표시가 메소드 선언 앞에 있으면 이것은 인스턴스 메소드라는 뜻이다.
- (void)setXLocation:(double)x Ylocation:(double)y;
메소드의 반환 형식은 실제 메소드 이름 앞에 괄호로 붙여 쓴다. 인자는 콜론 뒤에 쓰고, 만일 인자가 여러 개일 경우에는 공백으로 한 칸을 띄우고 인자의 이름을 쓰고 콜론을 뒤에 붙여 준다. 메소드 반환 형식과 같이 인자 데이터 형식은 인자의 이름 앞에 괄호를 붙여 써준다.

클래스를 상위 클래스와 연결하기 위해서는 반드시 슈퍼클래스의 인터페이스 파일을 불러들여야 한다. 보통은 슈퍼클래스 이름에 .h를 붙인 파일을 불러들인다.
#import "ItsSuperclass.h"
#import는 C 언어에서의 #include와 똑같은 역할을 하지만 조금더 똑똑하다. 파일이 한 번 읽어 들여졌다면 #include처럼 두 번 중복해서 불러들이지 않기 때문에 #import가 조금 더 똑똑하다고 할 수 있다.


Learning Cocoa

참고 도서

Learning Cocoa
Apple Computer, Inc.




구현(implementation) 파일

대부분의 클래스의 핵심은 구현 파일에서 구현된다. 이 파일에서 실제로 해당 내용을 수행하는 모든 메소드의 내용이 소스코드로 기록된다. 인터페이스 파일은 클래스를 사용하는 프로그래머가 실제 자신의 코드에서 사용하기 위해 알아야 하는 정보를 기술 하는 곳이고, 구현 파일은 실제 동작하는 코드를 쓰는 곳이다.

구현파일은 아래와 같이 인터페이스 파일과 비슷한 형식을 가진다
#import "ClassName.h"

@implementation ClassName : ItsSuperclass
{
instance variable declarations
}
method definitions
@end
모든 클래스 구현 파일은 자신의 인터페이스 파일을 불러들여야(import) 한다. 클래스에 대한 선언과 그 슈퍼 클래스에 대한 선언은 인터페이스 파일을 불러들이기 때문에 별도로 필요하지 않다. 이러한 구조는 구현 파일은 단지 메소드의 내용만 채워넣는다는 개념을 확실하게 해준다. 따라서 실제적으로 구현파일은 아래와 같은 코드만이 필요하다.
#import "ClassName.h"

@implementation ClassName
method definitions
@end
메소드는 C 함수와 같은 방식으로 정의된다. 함수의 이름은 마지막 세미콜론만 빼고 인터페이스 파일에서와 똑같이 사용한다. 그리고 메소드의 구현 코드는 함수 이름 다음 중괄호 안에 쓰면 된다. 예를 들어 Circle 클래스는 다음과 같은 메소드를 가진다.
+ alloc
{
your code
}

- (void)setXLocation:(double)x yLocation:(double)y
{
your code
}
위 코드에서 왜 + alloc는 반환 형식이 없는지 궁금할 것이다. Objective-C에서 반환 데이터 형식이 없을 때에는 기본적으로 id 형식이라 가정한다. 즉, 객체가 반환된다고 생각한다. + alloc 메소드는 클래스가 생성한 객체를 반환하는 것으로 되어 있어 특별히 반환 형식을 쓰지 않은 것이다. 덧붙여 실질적으로는 + alloc 메소드는 직접 구현할 필요가 없다는 사실을 알아둘 필요가 있다. NSObject 클래스에 이미 다 구현되어 있기 때문이다. + alloc 메소드의 목적은 새롭게 만들어지는 객체를 위해서 메모리 내에 공간을 할당하는 역할을 한다. 이것에 대해서는 이후에 객체를 생성하는 것에 대해 좀더 자세히 다룰 것이다.

메소드 선언에서 인스턴스 변수는 직접 사용할 수 있다. 모든 인스턴스 변수는 특별히 기술하지 않아도 모든 메소드 내에서 자유롭게 사용할 수 있다. C언어에서 전역 변수와 같다고 생각할 수 있다.(† 역자 주: 조금은 오해의 소지가 있다. 주의해서 이해하자) 따라서 두 번째 메소드는 다음과 같이 선언할 수 있다.
- (void)setXLocation:(double)x yLocation:(double)y
{
xLocation = x;
yLocation = y;
}
각 메소드 내에서만 사용 가능한 지역 변수도 선언할 수 있다. 예를 들면, 위 두 번째 메소드에서 별로 쓸모없는 변수를 넣어서 쓸 수도 있다.
- (void)setXLocation:(double)x yLocation:(double)y
{
double tempX;
double tempY;

tempX = x;
tempY = y;

xLocation = tempX;
yLocation = tempY;
}
조금은 위험한 표현이지만, C 함수를 정의할 때 사용할 수 있는 모든 방법을 여기에서도 사용할 수 있다고 말 할 수도 있겠다.

새로운 객체 생성하기

새로운 객체는 생성하고자 하는 클래스의 클래스 객체에 alloc 메소드를 보냄으로써 만들 수 있다. 예를 들어 Circle 클래스의 객체를 생성하고 싶다면 아래와 같이 해주면 된다.
id aCircle;
aCircle = [Circle alloc];
기억해야 할 것은 alloc은 객체를 반환한다는 것, 따라서 따라서 새로 만들어지는 인스턴스를 지칭하는 변수는 id 형식이어야 한다는 것이다. 일단 인스턴스를 만들고 난 다음에는 이 객체의 인스턴스 변수들을 초기화 시켜야 한다. 이것은 새로 만든 객체에 init 메시지를 보내면 된다.
[aCircle init];
초기화는 객체의 메모리 할당을 한 후에 해야 한다. 일반적으로는 다음과 같은 형태로 새로운 객체를 생성하는 경우가 많다.
aCircle = [[Circle alloc] init];
기본적으로 init는 모든 인스턴스 변수를 0으로 만든다. 초기화 메소드를 직접 정의할 수도 있다. 이것을 생성자(constructor)라고 하고, 각 클래스에 맞게 만들면 된다. 기본적으로 생성자는 "init" 라는 이름으로 시작한다. 생성자는 인스턴스의 변수를 직접 사용해야 하기 때문에, 생성자는 반드시 인스턴스 메소드여야 한다. alloc가 클래스 메소드인 것과는 다르다는 점에 유의하자. 예를 들어 Circle 클래스에서 기본적으로 원주의 값을 10으로 설정하는 생성자를 정의할 수 있다. 메소드는 다음과 같이 만들면 된다.
- (void)initWithRadius:(double)r;
{
radius = r;
}
생성자 메소드에서 지정하지 않은 모든 인스턴스 변수는 0으로 초기화 된다.

프로젝트 빌더(Project Builder) 에서

지금까지 프로젝트 빌더에 대해 언급한 적이 없지만 오늘 주제와 관련하여 조금 이야기 하면, 프로젝트 빌더에는 클래스 선언을 위한 인터페이스와 구현 파일의 템플릿 파일이 있다. 이것을 이용하면 파운데이션 프레임워크의 NSObject를 상속하는 새로운 클래스 정의 파일을 얻을 수 있다. 이 생성된 파일에서 데이터 구조를 선언하고 메소드 내용만 채워 넣으면 된다. 이제는 클래스에 대해 소스 코드상에서 형태도 이해 하였고, 어떻게 하면 간단히 만들 수 있는지도 이해했을 것이다.

이제부터는 실제로 프로그램을 만드는데 알아야 할 것이 얼마 남지 않았다. 다음 기사부터는 인터페이스 빌더에 대해 설명할 것이다. 인터페이스 빌더는 설명하기 수월하지 않은 과제이다. 앞으로 계속될 기사에서는 2개의 실제 동작하는 응용 프로그램을 만들어 가면서 어떻게 인터페이스 빌더와 프로젝트 빌더를 이용하여 프로그램을 재밌게 만들 수 있는지 알아볼 것이다.
TAG :
댓글 입력
자료실

최근 본 책0