Objective-C ¶
이 항목은 작성 중입니다.
이 항목은 작성 중인 항목입니다. 내용이 불완전할 수 있습니다. 충돌의 위험이 있으니 history를 확인한 후 작성하는 것을 권장합니다. 이 문서가 오랫동안 편집되지 않았다면 이 틀을 제거해 주세요.
오브젝티브 씨라고 읽으면 된다. 애칭은 옵씨. 브래드 콕스(Brad Cox)와 톰 러브(Tom Love)가 만든, 20세기의 걸작 언어 Jr.라고 생각하면 당신은 멋쟁이.
안시(ANSI) C에 객체지향 층을 얹어 만들었다. 따라서 실행 시 반드시 런타임(libobjc.dylib 또는 objc.dll)의 도움을 받아야 작동 가능하다. 물론 런타임은 애플 오픈 소스 사이트에 공개돼있으므로 능력이 있다면 얼마든지 정적 라이브러리로 만들어 붙여버려도 된다.
역사 ¶
초창기(넥스트가 먹기 전?) 옵씨는 C에서 객체 지향을 지원하도록 문법을 확장한, 일종의 신택틱 슈거(syntactic sugar)로 구현됐다. 옵씨 컴파일러는 옵씨 코드를 읽어 C 코드를 뱉었고, C 컴파일러가 그걸 바이너리로 어셈블했다. 넥스트(NeXT Inc.)가 옵씨를 집어삼킨 이후부터는 옵씨 코드에서 바로 바이너리로 직행하는 컴파일러가 지원된다. 예전에는 GCC를 이용했지만, 요즘은 애플 주도의 LLVM/Clang을 이용해 컴파일한다. 물론 오픈 소스 LLVM/Clang 3와 Apple LLVM/Clang 4 모두 옵씨 코드를 컴파일할 수 있지만, 클로즈드 브랜치인 Apple LLVM/Clang 4가 훨씬 월등한 어셈블 능력을 보여준다고.
여담으로 FreeBSD 재단에서 오픈 소스 LLVM/Clang 3를 다루던 사람이 말하길, 참고삼아 GCC 4.2의 옵씨 담당 부분 소스 코드를 봤다가 기절할 뻔했다고. 무려 일만 줄이 넘어가는 단일 C 파일 하나로 옵씨의 모든 걸 처리했단다. 이 GCC 확장에 대해서는 여러 루머가 있는데, 가장 유명한 걸 고려한다면 잡스가 빡쳐서 엿먹어라 하는 수준으로 GCC에 똥을 던진 듯.
문법 ¶
다음 코드는 옵씨에서 클래스를 선언하는 방법을 보여준다:
// ZPExampleClass.h #import "ZPExampleSuperClass.h" #import "ZPExampleProtocol.h" @interface ZPExampleClass: ZPExampleSuperClass <ZPExampleProtocol> // 클래스 이름은 ZPExampleClass이고 ZPExampleSuperClass를 상속하며 ZPExampleProtocol 인터페이스를 구현할 의무가 있다. ZPExampleSuperClass는 NSObject를 상속한다고 가정하자. { // 이 안으로는 인스턴스 변수를 선언한다. @private char privateInstanceVariable; // 외부 프로그래머는 이 인스턴스 변수가 존재함을 알 수 있지만, 이 클래스 바깥에서 사용이 불가하다. @protected int protectedInstanceVariable; // 외부 프로그래머는 이 인스턴스 변수가 존재함을 알 수 있지만, 이 클래스를 상속해야만 사용 가능하다. @public int *publicInstanceVariable; // 개나 소나 다 쓸 수 있다. } // 인스턴스 변수 선언 끝. +(void)inJavaThisIsCalledAStaticMethod; // '클래스 메서드'라고 하며, 인스턴스가 아닌 클래스 단에서 호출 가능. 자바에는 '스태틱 메서드'라고 하는 비슷한 놈이 있다. -(int)thisIsANormalMethodTakingOneArgument:(int)anArgument; // 이 메서드의 이름은 thisIsANormalMethodTakingOneArgument:이다. 콜론까지 이름에 포함한다. -(char *)methodTakingTwoArgumentsStartingArgOne:(int)argOne andArgTwo:(char)argTwo; // 이름은 methodTakingTwoArgumentsStartingArgOne:andArgTwo:. -(void)willDoNothingWithArgOne:(char *)firstArg argTwo:(id)secondArg argThree:(void *)thirdImpact; -(void)takingVariableArgs:(int)requiredFirstArg, ...; @end
그리고 다음은 선언한 클래스를 정의하는 코드이다:
// ZPExampleClass.m #import "ZPExampleClass.h" @implementation ZPExampleClass { // 인스턴스 변수는 여기서도 선언할 수 있다. @private int privateInstanceVariable2; // 외부 프로그래머는 이 인스턴스 변수가 존재함을 알 수 없고, 쓸 수도 없다. @protected int protectedInstanceVariable2; // 외부 프로그래머는 이 인스턴스 변수가 존재함을 알 수 없고, 쓸 수도 없다. 상속받는 클래스야 쓸 수는 있겠지만, 존재 여부를 알지 못하니 무용지물. @public char *publicInstanceVariable2; // ....... 님 맞을래여? } // 인스턴스 변수 선언 끝. +(void)inJavaThisIsCalledAStaticMethod { NSLog(@"클래스 메서드 호출됨."); } -(int)thisIsANormalMethodTakingOneArgument:(int)anArgument { return anArgument + 1; } -(char *)methodTakingTwoArgumentsStartingArgOne:(int)argOne andArgTwo:(char)argTwo { char *tmp = (char *)malloc(sizeof(char) * (argOne + 1)); if(NULL != tmp) { for(int i = 0; i < argOne; ++i) tmp[i] = argTwo; } return tmp; } -(void)willDoNothingWithArgOne:(char *)firstArg argTwo:(id)secondArg argThree:(void *)thirdImpact { // 아무 것도 안 함. } -(void)takingVariableArgs:(int)requiredFirstArg, ... { // 추후 구현 예정. } @end
마지막으로 클래스 인스턴스화 및 사용법이다:
ZPExampleClass *asdf = [[ZPExampleClass alloc] init]; // +alloc과 -init은 NSObject에 선언돼있음. [asdf willDoNothingWithArgOne:"Ooh, sometimes, I get a good feeling, yeah." argTwo:nil argThree:NULL];
객체의 구조 ¶
옵씨에서 임의의 객체를 가리키는 객체 포인터
id
는 objc.h
에 다음과 같이 선언돼있다:#if !OBJC_TYPES_DEFINED /// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; /// Represents an instance of a class. struct objc_object { Class isa; }; /// A pointer to an instance of a class. typedef struct objc_object *id; #endif즉,
id
는 struct objc_object
에 대한 포인터이고, struct objc_object
는 무조건 그 첫 내부 변수로 struct objc_class
에 대한 포인터를 들고 있다. 사용자가 어떠한 사용자 정의 클래스를 만들더라도 무조건 첫 내부 변수는 isa
포인터이다. 이 isa
('이즈 어'라고 읽는다.) 포인터는 옵씨의 동작 전체에 관여하는 아주 중요한 포인터로, 이게 없으면 옵씨는 아무 것도 할 수가 없다. isa
포인터는 이 객체의 정체성을 나타내며, 이 객체의 클래스 객체를 가리킨다.클래스 객체 ¶
방금 클래스 객체라고 했다. 아니, 클래스면 클래스고 객체면 객체지, 도대체 클래스 객체란 뭐람? 자바나 C++로 객체지향을 시작한 사람들이 여기서 멘붕을 겪는다. 우리가 흔히 알기로는 클래스는 설계도이고, 이 설계도대로 공장에서 찍어낸 결과물이 객체이다. 하지만 옵씨에서는 클래스 그 자신도 객체이다. 그래서 우리가 어떠한 클래스의 인스턴스를 생성하면 그 인스턴스는 클래스 한정의 인스턴스 변수를 담고
isa
가 클래스 객체를 가리키는 형태로 메모리에 할당이 된다. 예를 들어보자. 다음 코드는 say
라는 메서드를 가진 VARedFox
클래스를 선언하며, 내부에는 int
형의 인스턴스 변수 하나를 가진다:{{{ // VARedFox.h @interface VARedFox: VAFox -(void)say; @end // VARedFox.m @implementation VARedFox [[include(틀:ProgrammingLanguage)]]