컴파일러 지시문

Objective-C 언어는 객체 지향형 언어이므로, 재이용 가능한 데이터와 제어를 패키지화한 템플릿을 제공할 수 있습니다. 이것을, 일반적으로 클래스라고 부르고, 클래스는 메모리 실체를 생성하기 위한 정보입니다.

클래스의 개념은, C 언어의 구조체를 발전시킨 것으로, 구조체와 같은 데이터에 덧붙혀, 데이터와 제어(함수)를 관련짓고 있습니다. 그 때문에, 코드는 항상 데이터를 처리하는 전용의 함수를 안전하게 호출할 수 있어서 일련의 기능을 하나의 하부조직으로서 제공할 수 있습니다.

클래스를 이용하려면 , 구조체와 같이 우선 선언이 필요합니다. Objective-C 언어의 클래스의 선언은, 예약어(reserved word)나 구문이 아닌 컴파일러 지시문을 이용합니다. 컴파일러 지시문은, 컴파일러에 Objective-C에 확장된 클래스의 선언이나 실장 등에 이용되는 @ 마크로 시작되는 특징을 가집니다.

클래스를 선언하는 컴파일러 지시문은 @interface 로 시작해서 @end 로 끝납니다. 이러한 사이에, 클래스에 관련지을 수 있는 변수 영역과 함수를 선언합니다. Objective-C 의 클래스 선언과 정의의 관계는 다른 언어에 비해 복잡해서, 처음은 당황할지도 모릅니다.

@interface 클래스명 : 부모클래스명
{
	인스턴스 변수 선언
	...
}
메소드 선언
@end

이 시점에서,C 언어계의 객체 지향 언어와는 선언 방법이 꽤 다른 것이 많아집니다. 인스턴스 변수란, 클래스에 관련된 변수로, 구조체의 멤버와 같은 존재입니다. 다만, 구조체와는 달라 인스턴스 변수에 클래스의 외부로부터 액세스 할 수 없습니다. 인스턴스 변수를 이용할 수 있는 것은, 원칙으로서 클래스에 관련된 메소드만으로 되어 있습니다. 인스턴스 변수가 존재하지 않는 경우는 인스턴스 변수 선언과 { } 를 생략 할 수 있습니다.

메소드란, 클래스에 관련지을 수 있었던 함수입니다. 통상의 함수와의 차이는, 메소드는 메소드를 호출한 클래스의 실체에 관련지을 수 있고 있기 때문에, 직접 인스턴스 변수에 액세스 할 수 있습니다.

클래스명 바로뒤에 부모클래스를 지정하는 것이 있는데, 부모클래스란 무엇일까요? 객체 지향은, 한 번 정의된 기능을 확장하는 계승으로 불리는 기능을 제공하지 않으면 안됩니다. 계승 관계는 자주 생물의 진화론에 비유할 수 있어 개나 고양이등의, 여러가지 실체에 공통되는 추상적인 기능을 포유류 클래스로서 정의해, 개클래스나 고양이 클래스는 포유류 클래스를 계승하도록 프로그램 합니다.

부모클래스를 지정하지 않는 클래스는, 다른 클래스의 정점이 되기 위해 루트 클래스로 불립니다. Java나 .NET 그럼, 플랫폼이 기본이 되는 루와 클래스를 제공하는 구조를 채용해,C++ 언어는 루트 클래스를 정하지 않고 루트 클래스를 개발자가 개발할 수 있도록 설계되고 있습니다. Objective-C는 그 중간적인 형태로, 원칙적으로는 루트 클래스에서 개발할 수 있습니다만, 사실상은 컴파일러등의 처리계가 루트 클래스를 제공하지 않으면 안됩니다.

루트 클래스는 처리계에 따라서 다릅니다만 GCC 이면 Object 클래스가, Mac OS X 의 Cocoa 환경이면 NSObject 클래스가 루트 클래스가 됩니다. 독자적인 루트 클래스를 개발하는 경우이면 친클래스를 지정하지 않습니다만, 통상은 처리계가 제공하는 루트 클래스를 계승합니다. 루트 클래스를 개발하는 경우만, 선언은 다음과 같이 되겠지요.

@interface 클래스명
{
	인스턴스 변수 선언
	...
}
메소드 선언
@end

왜, 루트 클래스를 계승하지 않으면 한되는지라고 하면, 루트 클래스는 클래스가 정상적으로 동작하기 위해서 필요한 기본적인 기능을 제공하는 역할이 있기 때문에입니다. 루트 클래스가 존재하지 않으면, 클래스의 실체를 작성하기 위한 번잡한 수속을 모든 클래스에서 기술해야 하게 되어 버립니다. 히프메모리의 확보나 해방등의 기본적인 처리 수속은, 루트 클래스에 맡겨 버리는 것으로, 개발자는 개발 목적의 기능을 기술하는 것에 집중할 수 있습니다.

인스턴스 변수의 선언은 통상의 변수와 같습니다만, 메소드 선언은 함수의 선언과는 많이 다릅니다. 이 메소드 선언 사양은 C++ 에 비해 Objective-C는 C 언어 프로그래머에게 어색하게 받아 들여지는 큰 요인의 하나라고 생각할 수 있습니다. 메소드 선언은 다음과 같이 됩니다.

- (반환값형) 메소드명 : 가인수 리스트 ... ;

왜, 함수의 선언이랑 다른지가 C 언어와의 하이브리드 언어로서의 사양상의 문제가 부각되고 있습니다. 최초의 마이너스 기호 - 는 이 메소드가 클래스의 실체인 오브젝트에 관련하는 것을 나타내고 있습니다. 통상의 메소드는 - 기호로 좋습니다만, 오브젝트는 아니고 클래스에 관련하는 메소드의 선언이면 + 기호를 지정합니다. - 기호의 메소드를 인스턴스 메소드라고 불러,+ 기호로 시작되는 메소드를 클래스 메소드라고 부릅니다. 인스턴스 메소드와 클래스 메소드의 차이는 나중에 자세하게 설명합니다만, 일반적인 메소드는 - 로부터 시작되는점 기억해 주세요.

반환값형을 봐도 알수있듯이, 메소드 선언에서는 형태를 ( ) 로 둘러쌉니다. 메소드가 인수를 받는 경우는, 콜론 : 에 이어 가인수 리스트를 지정합니다. 가인수 리스트도, 통상의 변수 선언과는 달리, 변수형을 ( ) 의 안으로 지정해, 계속해서 변수명을 지정합니다.

C 언어의 함수의 디폴트의 반환값은 int 형태였지만, 메소드의 반환값의 디폴트는 id 형이라고 하는 Objective-C에 추가된 오브젝트를 나타내는 범용형으로 되어 있습니다. 반환값형은 생략 할 수 있습니다만, 통상은 생략하기 보다는, 명시적으로 선언합니다. 반환값을 돌려주지 않는 경우는 (void) 를 지정합니다.

@interface Test : Object
- (void)method;
@end

이 Test 클래스의 선언은, 반환값을 돌려주지 않고, 인수를 받지 않는 method 메소드를 선언하는 단순한 클래스를 선언하고 있습니다. 클래스명이나 메소드명의 명명 규칙은, C 언어의 변수나 구조체명의 명명 규칙과 같고, 한번에 식별할 수 있는 알파벳으로부터 시작되는 식별자가 아니면 안됩니다. 다만, 습관적으로 클래스명은 대문자로부터 시작하고 메소드명은 소문자로부터 시작합니다.

그런데, 클래스와 메소드를 무사하게 선언할 수 있었습니다만, 메소드는 메소드의 이름과 형태만을 선언한 것만으로 처리 코드를 기술하는 정의가 존재하지 않습니다. 실은, Objective-C는 클래스의 선언과 정의가 완전하게 분리되어 있고, 클래스의 선언으로는 인스턴스 변수와 메소드의 선언 밖에 실시할 수 없습니다. 클래스를 선언으로부터 정의로 구체화하려면 @implementation 컴파일러 지시문을 이용해 클래스를 작성하지 않으면 안됩니다.

@implementation 클래스명
메소드 정의
...
@end

@implementation 컴파일러 지시문에서는 @interface에 선언한 클래스의 실장을 기술합니다. 선언한 메소드는, 이 장소에서 정의되지 않으면 안됩니다. 클래스에서 메소드가 선언되어 있지 않은 경우,@implementation 에 기술하는 것은 아무것도 없습니다만, 정의되어 있지 않은 클래스는 @implementation에 명시적으로 실장하지 않으면 안됩니다.


클래스의 인스턴스화

클래스를 선언, 및 정의를 하면, 클래스를 이용할 수 있게 됩니다만, 그대로는 사용할 수 없습니다. 클래스를 이용하기 위해서는 필요한 메모리 영역을 할당해 적절한 초기화를 실시해 처음 클래스가 정상적으로 동작하게 됩니다. 클래스의 선언 정보에 근거하고, 메모리를 할당하는 것을 인스턴스화라고 부르고, 확보된 메모리 영역을 인스턴스라고 부릅니다. 인스턴스라고 부르는 경우, 통상은 클래스의 정보에 근거해 확보된 실체를 나타냅니다만, 일반적으로는 오브젝트라고도 불립니다.

C++ 언어나 Java 언어 등, 일반적인 객체 지향형 언어이면 new 연산자가 존재해, 이 연산자가 클래스의 선언 정보에 근거해 인스턴스를 생성해, 초기화해 줍니다. 그러나, 놀랄 만한 것이 Objective-C 언어는, 인스턴스의 생성을 포함해서 클래스가 실시하지 않으면 안 되도록 정해져 있습니다. 그런데 , 클래스의 인스턴스화에는, 오브젝트에 필요한 사이즈를 조사하고 메모리를 할당하는 등, 번잡한 초기화 처리가 필요하게 됩니다. 이것을 모든 클래스에서 구현하는 것은 현실적이지 않기 때문에, 이 작업을 혼자서 맡아 주는 것이 루트 클래스의 존재입니다.

Object 클래스는, Object 클래스를 포함해서 그것을 계승하는 클래스의 인스턴스를 적절히 생성하는 alloc 클래스 메소드를 정의하고 있습니다. 통상의 인스턴스 메소드는, 메소드를 호출하기 위해서 인스턴스가 필요합니다만, 클래스 메소드는 인스턴스가 없어도 실행할 수 있다고 하는 성질이 있습니다. 그 때문에, alloc 메소드는 인스턴스가 존재하지 않아도 호출하는 것에 문제는 없습니다. alloc 메소드는, 인스턴스를 생성하기 위한 클래스 메소드이므로, 흔히 팩토리 메소드라고도 불리고 있습니다.

+ alloc;

이것이,Object 클래스에서 선언되어 있는 alloc 메소드입니다. + 로 시작되고 있기 때문에, 이 메소드는 클래스 메소드인 것을 어필하고 있습니다. 반환값형은 생략 되고 있으므로, 디폴트의 id 형태가 돌려주어집니다.

id 형은, 오브젝트를 나타내는 범용형으로, 일종의 void * 와 같은 것이라고 생각해 주세요. 즉, 오브젝트의 클래스형같은것 뭐든지, 모든 오브젝트는 id 형태의 변수에 보존할 수 있습니다.

그런데, 클래스를 생성하기 위해서 alloc 메소드를 호출하지 않으면 안됩니다만, 메소드는 어떻게 호출하는 것입니까. C++ 계의 객체 지향 언어를 경험한 많은 프로그래머는, 직관적으로 다음과 같은 코드를 상상하겠지요.

id obj = 클래스명.alloc();

유감스럽지만, Objective-C에서는, 많은 객체 지향형 언어로 채용되고 있는 이 기술 방법은 틀렸습니다. Objective-C에    클래스 메소드를 호출하려면 , 다음과 같이 기술하지 않으면 안됩니다.

[클래스명 메소드명:인수 리스트...]

이것은, Smalltalk 언어를 객체 지향 부분의 어원으로 하는 Objective-C 의 특징입니다. 인스턴스 메소드를 호출하는 경우는, 클래스명의 부분에 오브젝트를 지정하지 않으면 안됩니다. C 언어에서는 [ ] 는 배열의 요소를 지정하기 위해서 사용되고 있었습니다만, 컴파일러는 [ ] 의 전후가 조사하는 것으로 메소드의 호출인가, 배열의 요소 지정인가를 판단할 수 있습니다.

이, 메소드를 호출하기 위한 [ ] 를 메세지라고 부릅니다. Objective-C는 오브젝트로부터 (함수의 주소등을 이용해) 메소드를 직접 호출하는 것이 아니라, 오브젝트에 대해서 메소드에 관련하는 메세지를 송신하고 있습니다. 메세지에 의해서 오브젝트에 메세지가 송신되면, 오브젝트는 주어진 메세지에 따라서 적절한 메소드를 기동하려고 합니다. 이러한 동적인 구조는 유연성을 확보합니다만 C 언어의 함수의 호출에 비해, 오버헤드는 배이상 걸린다고 생각해 주세요. 물론, 컴파일러가 최적화등을 실시하는 경우는 이 마지막으로는 없습니다만, 메세지에 의한 동적인 메소드의 호출은, 실행시에 메소드를 검색하기 때문에 반드시 오버헤드가 걸립니다.

#import <stdio.h>
#import <objc/Object.h>

@interface Test : Object
- (void)method;
@end

@implementation Test
- (void)method {
	printf("Kitty on your lap\n");
}
@end

int main() {
	id obj = [Test alloc];
	[obj method];
	
	return 0;
}

이것이 간단한 Objective-C 에 의한 클래스의 실현입니다. @interface 에 의한 클래스의 선언으로는, 루트 클래스 Object를 계승한 Test 클래스를 선언하고 있습니다. Test 클래스에서는, 반환값을 돌려주지 않고 인수를 받지 않는 method 메소드를 선언하고 있습니다. 그 때문에 @implementation는 이 메소드를 구현하지 않으면 안됩니다.

클래스의 선언과 정의가 종료하면, 그 후, 그 클래스를 이용할 수 있게 됩니다. main() 메소드에서는, 오브젝트를 격납하는  id 형태의 변수 obj 를 선언해, 동시에 Test 클래스의 인스턴스를 생성해 이것에 대입하고 있습니다. [Test alloc]는 Test 클래스의 클래스 메소드 alloc를 호출한다는 의미입니다. Test 클래스에서 alloc는 정의가 었었지만, alloc 메소드는 Object 루트 클래스에서 정의되고 있기 때문에, 정상적으로 동작합니다. alloc 메소드에서는,Test 클래스의 선언 정보에 근거해 적절히 메모리를 할당해 생성한 오브젝트를 돌려줍니다.

Test 클래스는 인스턴스 메소드 method를 정의하고 있기 때문에, 생성한 오브젝트의 method 메소드를 메세지로 기동할 수 있습니다. 프로그램에서는, 인스턴스 생성 직후에 [obj method] 과 같은 메세지형태로 기술하고, Test 클래스의 method 메소드를 기동하고 있습니다. 그 결과, 화면에는 Kitty on your lap 과 같은 문자열이 표시되겠지요.

덧붙여서, 오브젝트를 일회용으로 해 버린다면, 인스턴스의 생성과 method 메소드의 호출은, 다음과 같이 기술하는 것도 가능합니다.

[[Test alloc] method]

이 경우, 안쪽의 [Test alloc]가 먼저 실행되고, 생성한 인스턴스를 참조한 id 형태의 오브젝트가 돌려주어집니다. 그리고, 반환된 id 형태의 오브젝트에 대해서, 한번더 method 메세지를 송신하는 것으로, 메소드를 호출하고 있습니다. 이와 같이, 메세지는 내부에 한번더 기술할 수 있습니다. 이것은, (Test.alloc()).method() 와 같이 함수의 호출 관계에 비유할 수 있겠지요.

상기의 프로그램과 같이, 클래스의 선언과 정의의 장소는 자유롭습니다만, 습관으로서 클래스의 선언은 헤더 파일에, 정의는 클래스명과 같은 이름의 *.m 파일에 기술합니다.

원문 http://wisdom.sakura.ne.jp/programming/objc/objc3.html
번역의 귀차니즘으로 번역기 돌린 후 원문과 대조하면서 어색한 부분만 재수정한겁니다.
어휘 사용에서 다소 어색한 부분이 있더라도 양해바랍니다.

Posted by tklee

댓글을 달아 주세요