E D R , A S I H C RSS

Objective-C (rev. 1.4)

Objective-C

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에 똥을 던진 듯.

문법

C 소스 코드는 곧 옵씨 소스 코드이다. 앞서 말했듯, 옵씨는 C에 확장을 얇게 바른 언어에 가깝다. 하지만 그렇다고 옵씨 소스 코드가 곧 C 소스 코드인 건 아니다.
이 단에서는 옵씨만의 고유한 문법과 기호에 대해 설명한다.

클래스 선언 및 정의

클래스 선언

클래스 선언은 외부로 노출 가능한 클래스 명세를 의미한다. 즉, 외부의 프로그래머들에게 '이 클래스는 이러이러한 기능을 가지고 있다'고 알리는 목적이다.
클래스 선언에 쓰이는 옵씨 고유의 문법은 @interface 디렉티브와 @end 디렉티브이다.

@interface 디렉티브는 선언하는 클래스의 이름과 상속하는 부모 클래스 이름, 그리고 반점으로 구분된 준수할 프로토콜 이름의 목록을 인자로 받는다. 형식은 다음과 같다:
@interface 클래스 이름 : 부모 클래스 이름 <준수할 프로토콜 이름 목록>
여기서 부모 클래스 이름과 준수할 프로토콜 이름 목록은 선택적이다. 즉, 어떠한 클래스도 상속하지 않아 그 자신이 최상위 뿌리(root) 클래스가 될 수도 있고 어떠한 프로토콜도 준수하지 않을 수 있다. 아래에 몇 가지 예제가 있다:
// 뿌리 클래스
@interface ParentClass1
@end

// 뿌리 클래스를 상속한 클래스
@interface ChildClass1: ParentClass1
@end

// 또 다른 뿌리 클래스이면서 동시에 Protocol1을 준수
@interface ParentClass2 <Protocol1>
@end

// 또 다른 뿌리 클래스를 상속하면서 동시에 Protocol2와 Protocol3를 준수
@interface ChildClass2: ParentClass2 <Protocol2, Protocol3>
@end

클래스 선언은 주로 헤더 파일에 넣는다.

메서드 선언

메서드 선언 역시 선언의 일부이므로 클래스 선언 안에 들어간다. 메서드 선언문의 형식은 다음과 같다:

//여기까지 작성함.

다음 코드는 옵씨에서 클래스를 선언하는 방법을 보여준다:
// 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];

구조



객체의 구조

옵씨에서 임의의 객체를 가리키는 객체 포인터 idobjc.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
즉, idstruct 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)]]
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:23:53
Processing time 0.0293 sec