티스토리 뷰

반 객체지향(Half-of-OOP)

가장 먼저 명심하자. 오브젝티브-C는 C언어의 슈퍼셋(Super Set)이고 C++의 객체 지향과는 거리가 멀다. 오브젝티브-C 언어는 마치 객체지향 프로그래밍(OOP, Object-Oriented Programming) 처럼 보이지만 객체지향 언어가 아니다. 그렇다고 완전히 함수형 언어도 아니다.

하지만, 오브젝티브-C는 객체지향의 가장 대표적인 특징인 상속(Inheritence)이 가능하고 인터페이스 구현이 가능하다. ANSI-C 입장에서 바라보면 상속과 인터페이스 구현은 함수형 언어로서 가당치도 않은 언어적 특성임에 틀림 없을 것이다. (이 문장의 인터페이스는 오브젝티브-C의 @protocol을 의미함.)

물론, C 언어에서도 구조체(struct), 포인터(pointer) 등을 조합하여 객체처럼 다룰 수 있지만, 오브젝티브-C 처럼 상속과 인터페이스의 구현은 객체지향 언어가 아님에도 불구하고 완전히 객체지향 프로그래밍 처럼 개발하는데 모자람이 없을 지경이다.

정리해 보면 오브젝티브-C의 객체지향 특징은 다음과 같다.

1. 상속성(Inheritance)

상속성은 부모의 특징을 물려 받음과 동시에 재정의(override), 부모 호출(super 또는 base)를 지원한다. 더 자세히 말할 필요는 없을 것 같아서 걍~ 넘어간다.

// 오브젝티브-C 의 상속
@interface MFAppController : NSObject
{
   // .. 생략 ..
}

2. @protocol - 인터페이스 구현

오브젝티브-C의 프로토콜(protocol)은 C#과 Java의 interface와 같지만, 한 가지 다른 점은 인터페이스에 정의된 메서드 구현이 필수가 아니라는 점이다. 프로토콜의 모든 메서드를 굳이 구현하기 싫으면 하지 않아도 오브젝티브-C는 너그럽게 용서해 준다.

어떻게 이런 것이 가능할까? 지난 아티클 ‘[Objective-C] 아름다운을 추구하는 오브젝티브-C 언어 1/N’ 에서 다룬 것 처럼 메시지를 기반으로 메서드 호출을 위임하는 언어적인 특성 때문에 가능하다. 모든 프로토콜의 메서드를 정의하든 말든 컴파일러는 컴파일 할 뿐이고, 런타임에서 메시지를 통해 메서드를 호출해서 메서드가 구현되지 않았으면 예외를 발생하던가 아니면 씹던가 하게 된다. (단, @optional과 @required 참고)

// 오브젝티브-C 프로토콜 및 다중 상속
@protocol Animal

   -  (void)say;

@end

@protocol Duck <Animal, NSObject>

    - (void)swim;

@end

3. @category - 확장 메서드

카테고리(@category)는 C#에서의 확장 메서드(Extension Methods)와 똑같다. 확장 메서드는 2008년 C# 3.0에 언어적인 특징으로 포함되었다. 이와 더불어 람다 표현식(Lambda Expression), 이 두 가지는 C# 코드로 표현한 라인 수를 급격히 줄일 수 있었고, 더 섬세한 객체지향 프로그램에 집중할 수 있게 해준다.

@interface NSArray (ConvertableArray)
- (NSDictionary*)ConvertToDictionary;
@end

@implementation NSArray (ConvertableArray)
- (NSDictionary*)ConvertToDictionary
{
   // .. 실제 구현 생략 ..
   return nil;
}
@end

int main(int argc, const char * argv[])
{
   @autoreleasepool {

       NSArray* arr = [NSArray arrayWithObjects:@"A", @"B", nil];
       NSDictionary* dic = [arr ConvertToDictionary];

       // TODO
   }
   return 0;
}

같은 코드로 C# 으로 변환하면 다음 코드와 같다.

public static class ConvertableArray
{
    public static IDictionary ConvertToDictionary(this IList list)
    {
        // .. 구현 생략 ..
        return null;
    }
}

class MainClass
{
    public static void Main (string[] args)
    {
        var arr = new List<String> { "A", "B" };
        var dic = arr.ConvertToDictionary ();
    }
}

하지만, 내부적으로 처리되는 매커니즘은 완전히 틀리다. 무엇이 그렇게도 완전히 틀릴까?

  • C# 확장 메서드는 정적 클래스의 정적 메서드로만 표현할 수 있다.
  • 오브젝티브-C 카테고리는 인스턴스 메서드이다.

그러므로 오브젝티브-C의 카테고리(@category)는 C#의 확장 메서드와 달리 인스턴스화 된 카테고리 메서드가 호출될 때 마다 참조 카운터가 증가한다. NSArray 객체가 인스턴스화 되었기 때문에 카테고리 메서드를 호출할 수 있다는 이야기가 된다.

카테고리의 메서드는 디버거를 통해 다음과 같이 역어셈블리 된다. 역어셈블리 코드를 좀 더 보기 편하기 위해 로컬 변수를 선언하여 살펴 보았다.

먼저 48만큼 메모리 사이즈를 확보한 다음 넘어온 값과 로컬 변수를 초기화하거나 값을 대입한다. rbp-8( =rdi)self 가 들어간 것이고, rbp-16 (=rsi)는 메서드 정보가 들어간다. rbp-20 (=$10)은 INT32 정수 값이 할당된다. 마지막으로 +48로 원래 스택 포인터로 돌아와 빈 값을 리턴하게 된다. 스택이 8씩 자라는 것은 필자가 소스코드를 64비트 대상으로 컴파일 했기 때문이다.

0x100000d40:  pushq  %rbp
0x100000d41:  movq   %rsp, %rbp
0x100000d44:  subq   $48, %rsp
0x100000d48:  movq   %rdi, -8(%rbp)
0x100000d4c:  movq   %rsi, -16(%rbp)
0x100000d50:  movl   $10, -20(%rbp)
0x100000d57:  movq   -8(%rbp), %rsi
0x100000d5b:  movq   %rsi, %rdi
0x100000d5e:  callq  0x100000eac               ; symbol stub for: objc_retain
0x100000d63:  leaq   -32(%rbp), %rdi
0x100000d67:  movabsq$0, %rsi
0x100000d71:  movq   %rax, -32(%rbp)
0x100000d75:  movl   $1, -36(%rbp)
0x100000d7c:  callq  0x100000eb8               ; symbol stub for: objc_storeStrong
0x100000d81:  movabsq$0, %rax
0x100000d8b:  addq   $48, %rsp
0x100000d8f:  popq   %rbp
0x100000d90:  ret    



댓글