E D R , A S I H C RSS

Objective-C

Difference between r1.3 and the current

@@ -1,126 +1,311 @@
= Objective-C =
[[include(틀:작성중)]]
 
[[TableOfContents]]
''오브젝티브 씨''라고 읽으면 된다. 애칭은 ''옵씨''. 브래드 콕스(Brad Cox)와 톰 러브(Tom Love)가 만든, '''20세기의 걸작 언어 Jr.'''라고 생각하면 당신은 멋쟁이.

안시(ANSI) [[C]]에 객체지향 층을 얹어 만들었다. 따라서 실행 시 반드시 런타임(libobjc.dylib 또는 objc.dll)의 도움을 받아야 작동 가능하다. 물론 런타임은 [http://opensource.apple.com/source/objc4/ 애플 오픈 소스 사이트]에 공개돼있으므로 '''능력이 있다면''' 얼마든지 정적 라이브러리로 만들어 붙여버려도 된다.
안시(ANSI) [[C]]에 객체지향 층을 얹어 만들었다. 따라서 실행 시 반드시 런타임(libobjc.dylib 또는 objc.dll)의 도움을 받아야 작동 가능하다. 그렇다고 [[Java]]처럼 가상 기계(VM) 위에서 돌아가는 건 아니고, 객체 간의 통신을 담당하는 작은 라이브러리에 링크되어야 한다. 물론 이 런타임 라이브러리는 [http://opensource.apple.com/source/objc4/ 애플 오픈 소스 사이트]에 공개돼있으므로 '''능력이 있다면''' 얼마든지 정적 라이브러리로 만들어 붙여버려도 된다.

== 역사 ==
초창기(넥스트가 먹기 전?) 옵씨는 C에서 객체 지향을 지원하도록 문법을 확장한, 일종의 신택틱 슈거(syntactic sugar) 구현됐다. 옵씨 컴파일러는 옵씨 코드를 읽어 C 코드를 뱉었고, C 컴파일러가 그걸 바이너리로 어셈블했다. 넥스트(NeXT Inc.)가 옵씨를 집어삼킨 이후부터는 옵씨 코드에서 바로 바이너리로 직행하는 컴파일러가 지원된다. 예전에는 GCC를 이용했지만, 요즘은 애플 주도의 LLVM/Clang을 이용해 컴파일한다. 물론 오픈 소스 LLVM/Clang 3와 Apple LLVM/Clang 4 모두 옵씨 코드를 컴파일할 수 있지만, 클로즈드 브랜치인 Apple LLVM/Clang 4가 훨씬 월등한 어셈블 능력을 보여준다고.
초기의 옵씨는 C에서 [[wiki:"OOP" 객체지향]]을 지원하도록 문법을 확장한, 일종의 문법적 편의사항(syntactic sugar)으로 구현됐다. 옵씨 컴파일러는 옵씨 코드를 읽어 C 코드를 뱉었고, C 컴파일러가 그걸 바이너리로 어셈블했다. 하지만 넥스트(NeXT Inc.)가 옵씨를 집어삼킨 이후, 옵씨 코드에서 바로 바이너리로 직행하는 컴파일러가 나왔다. 이를 위해, 예전에는 GCC를 이용했지만 요즘은 애플 주도의 [[LLVM/Clang]]을 이용해 컴파일한다. 물론 오픈 소스 LLVM/Clang 3와 Apple LLVM/Clang 4 모두 옵씨 코드를 컴파일할 수 있지만, 닫힌 가지인 Apple LLVM/Clang 4가 훨씬 월등한 어셈블 능력을 보여준다고. 애초에 버전부터 차이나니까.

여담으로 FreeBSD 재단에서 오픈 소스 LLVM/Clang 3를 다루던 사람이 [http://bsdtalk.blogspot.kr/2013/11/bsdtalk233-from-gcc-to-llvmclang-with.html 말하길], 참고삼아 GCC 4.2의 옵씨 담당 부분 소스 코드를 봤다가 기절할 뻔했다고. 무려 일만 줄이 넘어가는 '''단일''' C 파일 하나로 옵씨의 모든 걸 처리했단다. 이 GCC 확장에 대해서는 여러 루머가 있는데, 가장 유명한 걸 고려한다면 잡스가 빡쳐서 엿먹어라 하는 수준으로 GCC에 똥을 던진 듯.
여담으로 [[FreeBSD]] 재단에서 오픈 소스 LLVM/Clang 3를 다루던 사람이 [http://bsdtalk.blogspot.kr/2013/11/bsdtalk233-from-gcc-to-llvmclang-with.html 말하길], 참고삼아 [[GCC]] 4.2의 옵씨 담당 부분 소스 코드를 봤다가 기절할 뻔했다고. 무려 일만 줄이 넘어가는 '''단일''' C 파일 하나로 옵씨의 모든 걸 처리했단다. 이 GCC 확장에 대해서는 여러 루머가 있는데, 가장 유명한 걸 고려한다면 잡스가 빡쳐서 엿먹어라 하는 수준으로 GCC에 똥을 던진 듯.

== 문법 ==
다음 코드는 옵씨에서 클래스를 선언하는 방법을 보여준다:
[[C]] 소스 코드는 옵씨 소스 코드이다. 앞서 말했듯, 옵씨는 C에 확장을 얇게 바른 언어에 가깝다. 하지만 그렇다고 옵씨 소스 코드가 곧 C 소스 코드인 건 아니다.
이 단에서는 옵씨만의 고유한 문법과 기호에 대해 설명하며, 이후 모든 설명에서는 ARC 사용을 기본적으로 가정한다.
 
=== 선언부 ===
이 단에서 소개하는 내용은 주로 헤더 파일(*.h)에 들어간다.
 
==== 클래스 선언 ====
클래스 선언은 외부로 노출 가능한 클래스 명세를 의미한다. 즉, 외부의 프로그래머들에게 '이 클래스는 이러이러한 기능을 가지고 있다'고 알리는 목적이다.
클래스 선언에 쓰이는 옵씨 고유의 문법은 {{{@interface}}} 명령과 {{{@end}}} 명령이다.
 
{{{@interface}}} 명령은 선언하는 클래스의 이름과 상속하는 부모 클래스 이름, 그리고 쉼표로 구분된 준수할 프로토콜 이름의 목록을 인자로 받는다. 형식은 다음과 같다:
{{{
// ZPExampleClass.h
@interface 클래스 이름 : 부모 클래스 이름 <준수할 프로토콜 이름 목록>
}}}
여기서 부모 클래스 이름과 준수할 프로토콜 이름 목록은 선택적이다. 즉, 어떠한 클래스도 상속하지 않아 그 자신이 최상위 뿌리(root) 클래스가 될 수도 있고 어떠한 프로토콜도 준수하지 않을 수 있다.

#import "ZPExampleSuperClass.h"
#import "ZPExampleProtocol.h"
{{{@end}}} 명령은 {{{@interface}}} 명령의 영역을 닫는다.

@interface ZPExampleClass: ZPExampleSuperClass <ZPExampleProtocol> // 클래스 이름은 ZPExampleClass이고 ZPExampleSuperClass를 상속하며 ZPExampleProtocol 인터페이스를 구현할 의무가 있다. ZPExampleSuperClass는 NSObject를 상속한다고 가정하자.
{ // 이 안으로는 인스턴스 변수를 선언한다.
@private
char privateInstanceVariable; // 외부 프로그래머는 이 인스턴스 변수가 존재함을 알 수 있지만, 이 클래스 바깥에서 사용이 불가하다.
@protected
int protectedInstanceVariable; // 외부 프로그래머는 이 인스턴스 변수가 존재함을 알 수 있지만, 이 클래스를 상속해야만 사용 가능하다.
@public
int *publicInstanceVariable; // 개나 소나 다 쓸 수 있다.
} // 인스턴스 변수 선언 끝.
아래에 가지 예제가 있다:
{{{
// 뿌리 클래스
@interface ParentClass1
@end

+(void)inJavaThisIsCalledAStaticMethod; // '클래스 메서드'라고 하며, 인스턴스가 아닌 클래스 단에서 호출 가능. 자바에는 '스태틱 메서드'라고 하는 비슷한 놈이 있다.
// 뿌리 클래스를 상속한 클래스 
@interface ChildClass1: ParentClass1
@end

-(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, ...;
// 다른 뿌리 클래스이면서 동시에 Protocol1을 준수
@interface ParentClass2 <;Protocol1>
@end

// 또 다른 뿌리 클래스를 상속하면서 동시에 (상속받은) Protocol1과 Protocol2, Protocol3를 준수
@interface ChildClass2: ParentClass2 <Protocol2, Protocol3>
@end
}}}

그리고 다음은 선언한 클래스를 정의하는 코드이다:
==== 메서드 선언 ====
메서드 선언 역시 선언의 일부이므로 클래스 선언 안에 들어간다. 메서드 선언문의 형식은 다음과 같다:
{{{
// ZPExampleClass.m
// 클래스 메서드 선언문
+ (반환형) 메서드_이름 ;

#import "ZPExampleClass.h"
// 인스턴스 메서드 선언문
- (반환형) 메서드_이름 ;
}}}
선언문에서 `(반환형)` 부분은 생략 가능하다. 생략하면 기본 반환형인 {{{id}}}가 사용된다.

@implementation ZPExampleClass
{ // 인스턴스 변수는 여기서도 선언할 수 있다.
메서드 이름 부분은 다음과 같은 형식을 가진다:
{{{
// 알림: 읽기 쉽도록 메서드 이름에 언더스코어(_)를 포함시켰다.
// 즉, 여기서 메서드 이름의 언더스코어는 필수요소가 아닌, 메서드 이름의 일부이다.
 
// 인자를 받지 않는 경우.
메서드_이름
 
// 인자를 하나 받는 경우.
// 이 경우, 메서드 이름은 '메서드_이름과_인자:'가 된다.
메서드_이름과_인자:(인자형)인자_이름
 
// 인자를 두 개 받는 경우.
// 이 경우, 메서드 이름은 '메서드_이름과_인자1:인자2:'가 된다.
메서드_이름과_인자1:(인자형)인자_이름1 인자2:(인자형)인자_이름2
 
// 인자를 세 개 받는 경우.
// 이 경우, 메서드 이름은 '메서드_이름과_인자1:인자2:인자3:'이 된다.
메서드_이름과_인자1:(인자형)인자_이름1 인자2:(인자형)인자_이름2 인자3:(인자형)인자_이름3
 
// 이 정도면 이해했겠지.
}}}
 
시범삼아 메서드 몇 개를 선언해보겠다:
{{{
// 클래스 메서드. 반환형은 char *.
// 메서드 이름은 'iTakeNoParameter'.
+(char *)iTakeNoParameter;
 
// 클래스 메서드. 반환형은 id.
// 메서드 이름은 'allocWithZone:'.
// 하나의 인자를 받으며, 그 형은 NSZone *, 이름은 aZone.
+allocWithZone:(NSZone *)aZone;
+(id)allocWithZone:(NSZone *)aZone; // 같은 표현
 
// 인스턴스 메서드. 반환형은 NSInteger.
// 메서드 이름은 'numberOfCharacter:inString:'.
// 인자는 두 개를 받는다. 첫번째는 unichar형의 ch, 두번째는 NSString *형의 str.
-(NSInteger)numberOfCharacter:(unichar)ch inString:(NSString *)str;
 
// 인스턴스 메서드. 반환형은 없음.
// 메서드 이름은 'drawRectangleOfWidth:height:upperLeftX:upperLeftY:'.
// 인자는 네 개를 받는다. 모든 인자는 CGFloat형이고 그 이름은 각각 w, h, x, y이다.
-(void)drawRectangleOfWidth:(CGFloat)w height:(CGFloat)h upperLeftX:(CGFloat)x upperLeftY:(CGFloat)y;
}}}
콜론 역시 메서드 이름의 일부임을 유의하자.
 
옵씨는 메서드 오버로딩을 지원하지 않는다. 따라서 중복된 이름의 메서드를 선언하면 컴파일 오류가 발생한다.
또한, 클래스 선언에 동봉된 모든 메서드는 공개(public) 가시성을 갖는다. 비공개(private) 메서드는 추후 설명하겠다.
 
==== 인스턴스 변수 선언: 제1부 ====
인스턴스 변수는 두 곳에 선언할 수 있다. 첫번째는 {{{@interface}}} 명령 바로 다음이다:
{{{
@interface ExampleClass: ExampleSuperClass <ExampleProtocol>
{ // 인스턴스 변수 선언 시작
int exampleInteger; // struct를 만들 때처럼 변수를 선언해주면 된다.
char exampleCharacter;
NSString *exampleString;
// 기타 인스턴스 변수를 선언한다.
} // 인스턴스 변수 선언 끝
 
// 메서드 선언부
 
@end
}}}
 
추가적으로, ([[C++]]처럼) {{{@private}}}, {{{@protected}}}, {{{@public}}} 명령을 사용해서 인스턴스 변수의 가시성을 조절할 수 있다:
{{{
@interface ExampleClass: ExampleSuperClass <ExampleProtocol> {
double iAmProtectedByDefault; // 기본 가시성은 @protected이다.
@private
int privateInstanceVariable2; // 외부 프로그래머는인스턴스 변수가 존재함을 수 없고, 쓸 수도 없다. 
@protected
int protectedInstanceVariable2; // 외부 프로그래머는 인스턴스 변수가 존재함을 없고, 수도 없다. 상속받는 클래스야 수는 있겠지만, 존재 여부를 알지 못하니 무용지물.
int privateInteger; // @private 변수는클래스 소속의 메서드에서만 접근 가능하다.
char privateCharacter; 
@protected // 이렇게 다른 가시성 명령을 만나기 전까지는 이전 가시성 명령이 계속 적용된다. 
int protectedInteger; // @protected 변수는 클래스와 상속받은 하위 클래스 소속의 메서드에서만 접근 가능하다. 
char protectedCharacter;
@public
char *publicInstanceVariable2; // ....... 맞을래여?
} // 인스턴스 변수 선언 끝.
+(void)inJavaThisIsCalledAStaticMethod {
NSLog(@"클래스 메서드 호출됨.");
int publicInteger; // @public 변수는 개나 소나 접근 가능하다.
char publicCharacter;
@private
int privateInteger2;
@public
char publicCharacter2;
@protected
float protectedFloat;
int iAmProtectedToo;
}

-(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;
}
@end
}}}
가시성 명령은 어디까지나 선택적이다. 명시적인 가시성 명령이 없으면 인스턴스 변수에는 기본적으로 {{{@protected}}}가 적용된다.

-(void)willDoNothingWithArgOne:(char *)firstArg argTwo:(id)secondArg argThree:(void *)thirdImpact { 
// 아무 것도 . 
}
하지만 옵씨 고수들은 가시성을 제한하더라도 쓸데없이 내부 구조를 외부에 노출시킨다는 문제 때문에 {{{@interface}}} 내부에 인스턴스 변수 선언을 넣는 것에 반대하는 입장이다. 따라서 이렇게 할 수도 있다는 것만 알고 다음으로 넘어가도록 하자. 다른 방법은 클래스 정의 부분에서 알아볼 것이다.

-(void)takingVariableArgs:(int)requiredFirstArg, ... {
// 추후 구현 예정.
==== 프로퍼티 선언: 제1부 ====
종래의 [[자바]]나 C++ 등의 언어에서는 클래스 외부에서 보호된(protected 또는 private) 인스턴스 변수를 다루려면 '접근자(accessor 또는 getter)'와 '변경자(mutator 또는 setter)'라는 메서드를 통해야만 한다. (C++의 friend 개념은 일단 제외하자.) 물론 옵씨도 예외는 아니라서, 모든 인스턴스 변수를 {{{@public}}}으로 놓는 미친 짓을 하는 게 아니라면 접근자와 변경자를 써야 한다.
하지만 문제는 언제나 '''귀차니즘'''. 언제나 {{{-(int)getInt { return anInteger; }}}}나 {{{-(void)setInt:(int)newInt { anInteger = newInt; }}}} 같은 중복 코드를 작성하는 건 귀찮고 따분한 일이 아닐 수 없다.
그래서 옵씨 2.0부터 새로이 도입된 개념이 바로 '프로퍼티(property)'. {{{@property}}} 명령을 사용하면
1. 인자로 요청된 인스턴스 변수가 선언되지 않았을 경우 선언. (이 경우 해당 인스턴스 변수의 가시성은 정의되지 않는다. 그냥 이 변수는 존재하지 않는다고 생각하라.)
1. 접근성 설정.
1. (인스턴스 변수가 객체를 담는 경우) 메모리 관리 정책 설정.
1. 접근자와 변경자 자동 선언 및 정의(구현).
등의 작업을 간편하게 처리할 수 있다.
프로퍼티 선언문의 구조는 다음과 같다:
{{{
@property( 쉼표로 구분된 프로퍼티 설정 목록 ) 변수형 변수이름 ;
}}}
그리고 프로퍼티 선언문은 메서드 선언부에 같이 들어간다.
 
쉼표로 구분된 프로퍼티 설정 목록에는 다음 다섯 종류의 선택자가 들어갈 수 있다:
* 접근자 메서드 이름
* 형식: {{{getter = accessorName}}}
* 이 선택자가 없으면 기본 접근자 이름은 인스턴스 변수 이름과 똑같다.
* 예를 들어, 인스턴스 변수 이름이 각각 {{{name}}}과 {{{age}}}인 경우, 기본 접근자 이름 역시 {{{name}}}과 {{{age}}}이다. 
* 변형자 메서드 이름
* 형식: {{{setter = mutatorName}}}
* 이 선택자가 없으면 기본 변형자 이름은 {{{set변수명}}}의 형태가 된다. 
* 예를 들어, 인스턴스 변수 이름이 각각 {{{name}}}과 {{{age}}}일 경우, 기본 변형자 이름은 {{{setName}}}, {{{setAge}}}가 된다. 자동 캐멀 케이싱에 주의할 것.
* 접근성
* {{{readonly}}}: 접근자만 자동으로 생성되고 변형자는 생성되지 않는다. 이 경우, 아래 메모리 관리 정책은 쓸모가 없다.
* {{{readwrite}}}: 접근자와 변형자가 자동으로 생성된다.
* 위 두 선택자는 상호배타적(mutually exclusive)으로, 반드시 둘 중 하나만 들어가야 한다. 생략하면 기본적으로 {{{readwrite}}}이다.
* 메모리 관리 정책
* {{{assign}}}: 변형자는 인자로 받은 값을 인스턴스 변수에 단순 대입한다. 인자로 원시형을 받는 경우에는 반드시 이 선택자를 써야 한다. 인자로 객체를 받는 경우, 이 객체의 생명주기에 전혀 간섭할 수 없다.
* {{{retain}}}, {{{strong}}}: 변형자의 인자로 받은 객체에 대해 강한 소유권을 행사한다. 원시형에는 이 선택자를 적용할 수 없다. 이 변수가 어떠한 객체를 들고있는 동안 해당 객체는 절대 소멸되지 않는다.
* {{{copy}}}: 변형자의 인자로 받은 객체를 복제하여 사본 객체를 인스턴스 변수에 넣는다. 원시형에는 이 선택자를 적용할 수 없다. 사본 객체에 대해서는 강한 소유권을 행사한다. 원본 객체의 생명주기에는 간섭하지 않는다.
* {{{weak}}}: 변형자의 인자로 받은 객체에 대해 약한 소유권을 행사한다. 원시형에는 이 선택자를 적용할 수 없다. 객체는 강한 소유권을 행사하는 주체가 없어지는 즉시 소멸되는데, 이 때 약한 소유권을 행사하는 주체는 모두 {{{nil}}}로 재설정된다.
* 위 세 선택자는 상호배타적(mutually exclusive)으로, 반드시 셋 중 하나만 들어가야 한다. 생략하면 기본적으로 {{{assign}}}이다.
* 원자성(atomicity)
* {{{nonatomic}}}: 접근자/변형자가 인스턴스 변수에 접근할 때 잠금(lock)을 사용하지 않도록 한다. 다중 스레드 환경에서 사용할 때는 주의하여야 한다. 다만, 읽기 전용(readonly) 프로퍼티에 적용하면 소소하게나마 성능 향상을 꾀할 수 있다.
* '''주의''': {{{atomic}}} 선택자는 없다. 그냥 {{{nonatomic}}}을 쓰지 않으면 원자적으로(== lock 사용) 동작한다.
 
다음 예제는 몇 개의 프로퍼티를 선언한다:
{{{
@interface Developer: Employee <CoffeeConsuming> {
@private
NSInteger employeeSince;
BOOL fired;
}

// NSString *형의 name.
// 생성되는 접근자: -(NSString *)name;.
// 생성되는 변형자: 생성 안 됨.
// 인스턴스 변수 자동 생성 여부: 생성됨.
// 원자성: 원자적이지 않음.
@property(readonly, nonatomic) NSString *name;
 
// NSInteger형의 employeeSince.
// 생성되는 접근자: -(NSInteger)employeeSince;.
// 생성되는 변형자: 생성 안 됨.
// 인스턴스 변수 자동 생성 여부: 생성 안 됨.
// 원자성: 원자적임.
@property(readonly) NSInteger employeeSince;
 
// BOOL형의 fired.
// 생성되는 접근자: -(BOOL)isFired;.
// 생성되는 변형자: -(void)setFired:(BOOL)fireHim;.
// 인스턴스 변수 자동 생성 여부: 생성 안 됨.
// 메모리 관리 정책: 단순 대입.
// 원자성: 원자적이지 않음.
@property(readwrite, nonatomic, getter = isFired) BOOL fired;
 
// NSArray *형의 thingsToDo.
// 생성되는 접근자: -(NSArray *)thingsToDo;.
// 생성되는 변형자: -(void)assignThingsToDo:(NSArray *)jobs;.
// 인스턴스 변수 자동 생성 여부: 생성됨.
// 메모리 관리 정책: 복제.
// 원자성: 원자적임.
@property(readwrite, copy, setter = assignThingsToDo) NSArray *thingsToDo;
@end
}}}

마지막으로 클래스 인스턴스화 사용법이다:
=== 정의부 ===
이 단에서 소개하는 내용은 주로 구현 파일(*.m)에 들어간다.
 
==== 클래스 정의 ====
클래스를 선언했으면 정의도 해야 할 것이다. 다음과 같이 정의한다:
{{{
ZPExampleClass *asdf = [[ZPExampleClass alloc] init]; // +alloc과 -init은 NSObject에 선언돼있음.
[asdf willDoNothingWithArgOne:"Ooh, sometimes, I get a good feeling, yeah." argTwo:nil argThree:NULL];
@implementation 클래스 이름
}}}
역시나 {{{@end}}} 명령으로 {{{@implementation}}}의 영역을 닫는다.

== 구조 ==
C 소스 코드는 곧 옵씨 소스 코드이다. 앞서 말했듯, 옵씨는 C에 확장을 얇게 바른 언어에 가깝다. 하지만 그렇다고 옵씨 소스 코드가 곧 C 소스 코드인 건 아니다.
 
=== 객체의 구조 ===
옵씨에서 임의의 객체를 가리키는 객체 포인터 `id`는 `objc.h`에 다음과 같이 선언돼있다:
{{{
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
@implementation EmptyClass

/// Represents an instance of a class. 
struct objc_object {
Class isa;
};
// The Beauty of Emptiness.

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
@end
}}}
즉, `id`는 `struct objc_object`에 대한 포인터이고, `struct objc_object`는 무조건 그 첫 내부 변수로 `struct objc_class`에 대한 포인터를 들고 있다. 사용자가 어떠한 사용자 정의 클래스를 만들더라도 무조건 첫 내부 변수는 `isa` 포인터이다. 이 `isa`('이즈 어'라고 읽는다.) 포인터는 옵씨의 동작 전체에 관여하는 아주 중요한 포인터로, 이게 없으면 옵씨는 아무 것도 할 수가 없다. `isa` 포인터는 이 객체의 정체성을 나타내며, 이 객체의 클래스 객체를 가리킨다.

=== 클래스 객체 ===
방금 '''클래스 객체'''라고 했다. 아니, 클래스면 클래스고 객체면 객체지, 도대체 클래스 객체란 뭐람? 자바나 C++로 객체지향을 시작한 사람들이 여기서 멘붕을 겪는다. 우리가 흔히 알기로는 클래스는 설계도이고, 설계도대로 공장에서 찍어낸 결과물이 객체이다. 하지만 옵씨에서는 클래스 그 자신도 객체이다. 그래서 우리가 어떠한 클래스의 인스턴스를 생성하면 그 인스턴스는 클래스 한정의 인스턴스 변수를 담고 `isa`가 클래스 객체를 가리키는 형태로 메모리에 할당이 된다. 예를 들어보자. 다음 코드는 `say`라는 메서드를 가진 `VARedFox` 클래스를 선언하며, 내부에는 `int` 형의 인스턴스 변수 하나를 가진다:
==== 메서드 정의 ====
메서드 정의는 클래스 정의 내부에 들어간다. 정의할 때는 선언할 때처럼 메서드 이름과 인자 전체를 적고 세미콜론으로 마감하는 것이 아니라 중괄호를 열고 닫아 메서드 본체를 정의한다. 이해를 위해 예제로 설명하겠다. 윗부분 메서드 선언의 예제를 재활용하겠다:
{{{
// VARedFox.h
@interface VARedFox: VAFox
@implementation ExampleClass

-(void)say;
+(char *)iTakeNoParameter {
return "asdf";
}
 
+(id)allocWithZone:(NSZone *)aZone {
// Vendor specific implementation.
}
 
-(NSInteger)numberOfCharacter:(unichar)ch inString:(NSString *)str {
NSUInteger j = [str length], k = 0; 
unichar *asdf = (unichar *)malloc(sizeof(unichar) * j);
if(NULL == asdf)
return -1;
[str getCharacters:asdf range:NSRangeMake(0, j)];
for(NSUInteger i = 0; i < j; ++i) {
if(ch == asdf[i])
++k;
}
return k;
}
 
-(void)drawRectangleOfWidth:(CGFloat)w height:(CGFloat)h upperLeftX:(CGFloat)x upperLeftY:(CGFloat)y {
// implementation may vary.
}

@end
}}}

// VARedFox.m
@implementation VARedFox
==== 인스턴스 변수 선언: 제2부 ====
아까 위에서 말했듯, 클래스 선언부에 인스턴스 변수 선언을 같이 적는 것은 쓸데없는 상세 구현을 외부로 노출시키는 것이라 옵씨 고수들은 싫어한다. 따라서 애플은 옵씨 2.1 이후의 어떤 업데이트에서 클래스 구현에 인스턴스 변수 선언을 할 수 있도록 허용했다. 다음과 같이 한다:
{{{
@implementation ExampleClass
{ // 인스턴스 변수 선언 시작
int protected; // 역시 기본 가시성은 protected이다. 하지만.......
@public
char inaccessibleFromOutside; // @public 변수가 구현부에 숨어있어서, 외부에서 절대 접근이 불가능하다.
@protected
long protectedButInaccessibleFromChilds; // @protected 변수가 구현부에 숨어있어서, 상속하는 클래스는 접근할 수 없다.
@private
unsigned long long private;
} // 인스턴스 변수 선언

@end
}}}
 
==== 프로퍼티 선언: 제2부 ====

[[include(틀:ProgrammingLanguage)]]


이 항목은 작성 중입니다.

이 항목은 작성 중인 항목입니다. 내용이 불완전할 수 있습니다. 충돌의 위험이 있으니 history를 확인한 후 작성하는 것을 권장합니다. 이 문서가 오랫동안 편집되지 않았다면 이 틀을 제거해 주세요.





오브젝티브 씨라고 읽으면 된다. 애칭은 옵씨. 브래드 콕스(Brad Cox)와 톰 러브(Tom Love)가 만든, 20세기의 걸작 언어 Jr.라고 생각하면 당신은 멋쟁이.

안시(ANSI) C에 객체지향 층을 얹어 만들었다. 따라서 실행 시 반드시 런타임(libobjc.dylib 또는 objc.dll)의 도움을 받아야 작동 가능하다. 그렇다고 Java처럼 가상 기계(VM) 위에서 돌아가는 건 아니고, 객체 간의 통신을 담당하는 작은 라이브러리에 링크되어야 한다. 물론 이 런타임 라이브러리는 애플 오픈 소스 사이트에 공개돼있으므로 능력이 있다면 얼마든지 정적 라이브러리로 만들어 붙여버려도 된다.

1. 역사

초기의 옵씨는 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에 똥을 던진 듯.

2. 문법

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

2.1. 선언부

이 단에서 소개하는 내용은 주로 헤더 파일(*.h)에 들어간다.

2.1.1. 클래스 선언

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

@interface 명령은 선언하는 클래스의 이름과 상속하는 부모 클래스 이름, 그리고 쉼표로 구분된 준수할 프로토콜 이름의 목록을 인자로 받는다. 형식은 다음과 같다:
@interface 클래스 이름 : 부모 클래스 이름 <준수할 프로토콜 이름 목록>
여기서 부모 클래스 이름과 준수할 프로토콜 이름 목록은 선택적이다. 즉, 어떠한 클래스도 상속하지 않아 그 자신이 최상위 뿌리(root) 클래스가 될 수도 있고 어떠한 프로토콜도 준수하지 않을 수 있다.

@end 명령은 @interface 명령의 영역을 닫는다.

아래에 몇 가지 예제가 있다:
// 뿌리 클래스
@interface ParentClass1
@end

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

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

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

2.1.2. 메서드 선언

메서드 선언 역시 선언의 일부이므로 클래스 선언 안에 들어간다. 메서드 선언문의 형식은 다음과 같다:
// 클래스 메서드 선언문
+ (반환형) 메서드_이름 ;

// 인스턴스 메서드 선언문
- (반환형) 메서드_이름 ;
선언문에서 (반환형) 부분은 생략 가능하다. 생략하면 기본 반환형인 id가 사용된다.

메서드 이름 부분은 다음과 같은 형식을 가진다:
// 알림: 읽기 쉽도록 메서드 이름에 언더스코어(_)를 포함시켰다.
// 즉, 여기서 메서드 이름의 언더스코어는 필수요소가 아닌, 메서드 이름의 일부이다.

// 인자를 받지 않는 경우.
메서드_이름

// 인자를 하나 받는 경우.
// 이 경우, 메서드 이름은 '메서드_이름과_인자:'가 된다.
메서드_이름과_인자:(인자형)인자_이름

// 인자를 두 개 받는 경우.
// 이 경우, 메서드 이름은 '메서드_이름과_인자1:인자2:'가 된다.
메서드_이름과_인자1:(인자형)인자_이름1 인자2:(인자형)인자_이름2

// 인자를 세 개 받는 경우.
// 이 경우, 메서드 이름은 '메서드_이름과_인자1:인자2:인자3:'이 된다.
메서드_이름과_인자1:(인자형)인자_이름1 인자2:(인자형)인자_이름2 인자3:(인자형)인자_이름3

// 이 정도면 이해했겠지.

시범삼아 메서드 몇 개를 선언해보겠다:
// 클래스 메서드. 반환형은 char *.
// 메서드 이름은 'iTakeNoParameter'.
+(char *)iTakeNoParameter;

// 클래스 메서드. 반환형은 id.
// 메서드 이름은 'allocWithZone:'.
// 하나의 인자를 받으며, 그 형은 NSZone *, 이름은 aZone.
+allocWithZone:(NSZone *)aZone;
+(id)allocWithZone:(NSZone *)aZone; // 같은 표현

// 인스턴스 메서드. 반환형은 NSInteger.
// 메서드 이름은 'numberOfCharacter:inString:'.
// 인자는 두 개를 받는다. 첫번째는 unichar형의 ch, 두번째는 NSString *형의 str.
-(NSInteger)numberOfCharacter:(unichar)ch inString:(NSString *)str;

// 인스턴스 메서드. 반환형은 없음.
// 메서드 이름은 'drawRectangleOfWidth:height:upperLeftX:upperLeftY:'.
// 인자는 네 개를 받는다. 모든 인자는 CGFloat형이고 그 이름은 각각 w, h, x, y이다.
-(void)drawRectangleOfWidth:(CGFloat)w height:(CGFloat)h upperLeftX:(CGFloat)x upperLeftY:(CGFloat)y;
콜론 역시 메서드 이름의 일부임을 유의하자.

옵씨는 메서드 오버로딩을 지원하지 않는다. 따라서 중복된 이름의 메서드를 선언하면 컴파일 오류가 발생한다.
또한, 클래스 선언에 동봉된 모든 메서드는 공개(public) 가시성을 갖는다. 비공개(private) 메서드는 추후 설명하겠다.

2.1.3. 인스턴스 변수 선언: 제1부

인스턴스 변수는 두 곳에 선언할 수 있다. 첫번째는 @interface 명령 바로 다음이다:
@interface ExampleClass: ExampleSuperClass <ExampleProtocol>
{ // 인스턴스 변수 선언 시작
        int exampleInteger; // struct를 만들 때처럼 변수를 선언해주면 된다.
        char exampleCharacter;
        NSString *exampleString;
        // 기타 인스턴스 변수를 선언한다.
} // 인스턴스 변수 선언 끝

// 메서드 선언부

@end

추가적으로, (C++처럼) @private, @protected, @public 명령을 사용해서 인스턴스 변수의 가시성을 조절할 수 있다:
@interface ExampleClass: ExampleSuperClass <ExampleProtocol> {
        double iAmProtectedByDefault; // 기본 가시성은 @protected이다.
@private
        int privateInteger; // @private 변수는 이 클래스 소속의 메서드에서만 접근 가능하다.
        char privateCharacter;
@protected // 이렇게 다른 가시성 명령을 만나기 전까지는 이전 가시성 명령이 계속 적용된다.
        int protectedInteger; // @protected 변수는 이 클래스와 상속받은 하위 클래스 소속의 메서드에서만 접근 가능하다.
        char protectedCharacter;
@public
        int publicInteger; // @public 변수는 개나 소나 다 접근 가능하다.
        char publicCharacter;
@private
        int privateInteger2;
@public
        char publicCharacter2;
@protected
        float protectedFloat;
        int iAmProtectedToo;
}

// 메서드 선언부

@end
가시성 명령은 어디까지나 선택적이다. 명시적인 가시성 명령이 없으면 인스턴스 변수에는 기본적으로 @protected가 적용된다.

하지만 옵씨 고수들은 가시성을 제한하더라도 쓸데없이 내부 구조를 외부에 노출시킨다는 문제 때문에 @interface 내부에 인스턴스 변수 선언을 넣는 것에 반대하는 입장이다. 따라서 이렇게 할 수도 있다는 것만 알고 다음으로 넘어가도록 하자. 다른 방법은 클래스 정의 부분에서 알아볼 것이다.

2.1.4. 프로퍼티 선언: 제1부

종래의 나 C++ 등의 언어에서는 클래스 외부에서 보호된(protected 또는 private) 인스턴스 변수를 다루려면 '접근자(accessor 또는 getter)'와 '변경자(mutator 또는 setter)'라는 메서드를 통해야만 한다. (C++의 friend 개념은 일단 제외하자.) 물론 옵씨도 예외는 아니라서, 모든 인스턴스 변수를 @public으로 놓는 미친 짓을 하는 게 아니라면 접근자와 변경자를 써야 한다.
하지만 문제는 언제나 귀차니즘. 언제나 -(int)getInt { return anInteger; }나 -(void)setInt:(int)newInt { anInteger = newInt; } 같은 중복 코드를 작성하는 건 귀찮고 따분한 일이 아닐 수 없다.
그래서 옵씨 2.0부터 새로이 도입된 개념이 바로 '프로퍼티(property)'. @property 명령을 사용하면
  1. 인자로 요청된 인스턴스 변수가 선언되지 않았을 경우 선언. (이 경우 해당 인스턴스 변수의 가시성은 정의되지 않는다. 그냥 이 변수는 존재하지 않는다고 생각하라.)
  2. 접근성 설정.
  3. (인스턴스 변수가 객체를 담는 경우) 메모리 관리 정책 설정.
  4. 접근자와 변경자 자동 선언 및 정의(구현).
등의 작업을 간편하게 처리할 수 있다.
프로퍼티 선언문의 구조는 다음과 같다:
@property( 쉼표로 구분된 프로퍼티 설정 목록 ) 변수형 변수이름 ;
그리고 프로퍼티 선언문은 메서드 선언부에 같이 들어간다.

쉼표로 구분된 프로퍼티 설정 목록에는 다음 다섯 종류의 선택자가 들어갈 수 있다:
  • 접근자 메서드 이름
    • 형식: getter = accessorName
    • 이 선택자가 없으면 기본 접근자 이름은 인스턴스 변수 이름과 똑같다.
      • 예를 들어, 인스턴스 변수 이름이 각각 nameage인 경우, 기본 접근자 이름 역시 nameage이다.
  • 변형자 메서드 이름
    • 형식: setter = mutatorName
    • 이 선택자가 없으면 기본 변형자 이름은 set변수명의 형태가 된다.
      • 예를 들어, 인스턴스 변수 이름이 각각 nameage일 경우, 기본 변형자 이름은 setName, setAge가 된다. 자동 캐멀 케이싱에 주의할 것.
  • 접근성
    • readonly: 접근자만 자동으로 생성되고 변형자는 생성되지 않는다. 이 경우, 아래 메모리 관리 정책은 쓸모가 없다.
    • readwrite: 접근자와 변형자가 자동으로 생성된다.
    • 위 두 선택자는 상호배타적(mutually exclusive)으로, 반드시 둘 중 하나만 들어가야 한다. 생략하면 기본적으로 readwrite이다.
  • 메모리 관리 정책
    • assign: 변형자는 인자로 받은 값을 인스턴스 변수에 단순 대입한다. 인자로 원시형을 받는 경우에는 반드시 이 선택자를 써야 한다. 인자로 객체를 받는 경우, 이 객체의 생명주기에 전혀 간섭할 수 없다.
    • retain, strong: 변형자의 인자로 받은 객체에 대해 강한 소유권을 행사한다. 원시형에는 이 선택자를 적용할 수 없다. 이 변수가 어떠한 객체를 들고있는 동안 해당 객체는 절대 소멸되지 않는다.
    • copy: 변형자의 인자로 받은 객체를 복제하여 사본 객체를 인스턴스 변수에 넣는다. 원시형에는 이 선택자를 적용할 수 없다. 사본 객체에 대해서는 강한 소유권을 행사한다. 원본 객체의 생명주기에는 간섭하지 않는다.
    • weak: 변형자의 인자로 받은 객체에 대해 약한 소유권을 행사한다. 원시형에는 이 선택자를 적용할 수 없다. 객체는 강한 소유권을 행사하는 주체가 없어지는 즉시 소멸되는데, 이 때 약한 소유권을 행사하는 주체는 모두 nil로 재설정된다.
    • 위 세 선택자는 상호배타적(mutually exclusive)으로, 반드시 셋 중 하나만 들어가야 한다. 생략하면 기본적으로 assign이다.
  • 원자성(atomicity)
    • nonatomic: 접근자/변형자가 인스턴스 변수에 접근할 때 잠금(lock)을 사용하지 않도록 한다. 다중 스레드 환경에서 사용할 때는 주의하여야 한다. 다만, 읽기 전용(readonly) 프로퍼티에 적용하면 소소하게나마 성능 향상을 꾀할 수 있다.
    • 주의: atomic 선택자는 없다. 그냥 nonatomic을 쓰지 않으면 원자적으로(== lock 사용) 동작한다.

다음 예제는 몇 개의 프로퍼티를 선언한다:
@interface Developer: Employee <CoffeeConsuming> {
@private
        NSInteger employeeSince;
        BOOL fired;
}

// NSString *형의 name.
// 생성되는 접근자: -(NSString *)name;.
// 생성되는 변형자: 생성 안 됨.
// 인스턴스 변수 자동 생성 여부: 생성됨.
// 원자성: 원자적이지 않음.
@property(readonly, nonatomic) NSString *name;

// NSInteger형의 employeeSince.
// 생성되는 접근자: -(NSInteger)employeeSince;.
// 생성되는 변형자: 생성 안 됨.
// 인스턴스 변수 자동 생성 여부: 생성 안 됨.
// 원자성: 원자적임.
@property(readonly) NSInteger employeeSince;

// BOOL형의 fired.
// 생성되는 접근자: -(BOOL)isFired;.
// 생성되는 변형자: -(void)setFired:(BOOL)fireHim;.
// 인스턴스 변수 자동 생성 여부: 생성 안 됨.
// 메모리 관리 정책: 단순 대입.
// 원자성: 원자적이지 않음.
@property(readwrite, nonatomic, getter = isFired) BOOL fired;

// NSArray *형의 thingsToDo.
// 생성되는 접근자: -(NSArray *)thingsToDo;.
// 생성되는 변형자: -(void)assignThingsToDo:(NSArray *)jobs;.
// 인스턴스 변수 자동 생성 여부: 생성됨.
// 메모리 관리 정책: 복제.
// 원자성: 원자적임.
@property(readwrite, copy, setter = assignThingsToDo) NSArray *thingsToDo;

@end

2.2. 정의부

이 단에서 소개하는 내용은 주로 구현 파일(*.m)에 들어간다.

2.2.1. 클래스 정의

클래스를 선언했으면 정의도 해야 할 것이다. 다음과 같이 정의한다:
@implementation 클래스 이름
역시나 @end 명령으로 @implementation의 영역을 닫는다.

@implementation EmptyClass

// The Beauty of Emptiness.

@end

2.2.2. 메서드 정의

메서드 정의는 클래스 정의 내부에 들어간다. 정의할 때는 선언할 때처럼 메서드 이름과 인자 전체를 적고 세미콜론으로 마감하는 것이 아니라 중괄호를 열고 닫아 메서드 본체를 정의한다. 이해를 위해 예제로 설명하겠다. 윗부분 메서드 선언의 예제를 재활용하겠다:
@implementation ExampleClass

+(char *)iTakeNoParameter {
        return "asdf";
}

+(id)allocWithZone:(NSZone *)aZone {
        // Vendor specific implementation.
}

-(NSInteger)numberOfCharacter:(unichar)ch inString:(NSString *)str {
        NSUInteger j = [str length], k = 0;
        unichar *asdf = (unichar *)malloc(sizeof(unichar) * j);
        if(NULL == asdf)
                return -1;
        
        [str getCharacters:asdf range:NSRangeMake(0, j)];
        
        for(NSUInteger i = 0; i < j; ++i) {
                if(ch == asdf[i])
                        ++k;
        }
        
        return k;
}

-(void)drawRectangleOfWidth:(CGFloat)w height:(CGFloat)h upperLeftX:(CGFloat)x upperLeftY:(CGFloat)y {
        // implementation may vary.
}

@end

2.2.3. 인스턴스 변수 선언: 제2부

아까 위에서 말했듯, 클래스 선언부에 인스턴스 변수 선언을 같이 적는 것은 쓸데없는 상세 구현을 외부로 노출시키는 것이라 옵씨 고수들은 싫어한다. 따라서 애플은 옵씨 2.1 이후의 어떤 업데이트에서 클래스 구현에 인스턴스 변수 선언을 할 수 있도록 허용했다. 다음과 같이 한다:
@implementation ExampleClass
{ // 인스턴스 변수 선언 시작
        int protected; // 역시 기본 가시성은 protected이다. 하지만.......
@public
        char inaccessibleFromOutside; // @public 변수가 구현부에 숨어있어서, 외부에서 절대 접근이 불가능하다.
@protected
        long protectedButInaccessibleFromChilds; // @protected 변수가 구현부에 숨어있어서, 상속하는 클래스는 접근할 수 없다.
@private
        unsigned long long private;
} // 인스턴스 변수 선언 끝

@end

2.2.4. 프로퍼티 선언: 제2부



언어의 종류
머신 친화적C가족
MachineLangaugeAssemblyCC++Objective-CObjective-C++씨앗
체지향함수형 언어
SmallTalkJAVAC#LispHaskellScala
스크립트언어
JavaScriptCoffeeScriptPerlLuaPHPPythonPyPyRubyShellScriptMATLAB
미분류
ErlangGoJOCamlScalaRCSSMLAdaProlog
ScratchRustless
[http]프로그래밍 언어 목록


Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:23:53
Processing time 0.1757 sec