Devin.KR

Objective-C · 기본

Objective-C 문법과 Cocoa 기초

클래스 정의 - 헤더와 구현 나누기

@interface 와 @implementation, .h·.m 분리, 인스턴스 변수, 메서드 선언과 구현, self, 여러 파일 컴파일

개발자KR · 원고 갱신

이 장에서 배우는 것

앞 장에서는 대괄호 문법으로 객체에 메시지를 보내는 방법을 익혔다. 그때 쓴 객체는 Foundation이 미리 만들어 둔 것이었다. 이 장에서는 우리가 직접 클래스(class)를 만든다. 클래스는 객체를 찍어 내는 설계도이고, 설계도에는 객체가 가질 값과 객체가 할 수 있는 일이 함께 적힌다. Objective-C는 이 설계도를 두 부분으로 나누어 쓴다. 바깥에 보여 줄 약속은 헤더 파일(.h)에, 실제 동작은 구현 파일(.m)에 둔다.

  • @interface 와 @implementation 이 각각 무엇을 적는 자리인지 설명할 수 있다.
  • 인스턴스 변수(instance variable)를 선언하고 메서드 안에서 읽고 쓸 수 있다.
  • 메서드 선언을 읽고, 같은 모양으로 구현을 쓸 수 있다.
  • 메서드 안에서 self 가 가리키는 대상을 말할 수 있다.
  • 여러 개의 .h·.m 파일을 한 번에 컴파일해 실행 파일을 만들 수 있다.

문제 상황

사내 도서 대출 관리 도구를 만든다고 하자. 처음에는 main.m 한 파일에 책 제목을 담은 변수, 대출 중인지 나타내는 변수, 대출을 처리하는 함수를 늘어놓는다. 책이 세 권이면 변수가 아홉 개가 되고, 회원이 생기면 같은 일이 되풀이된다. 어느 변수가 어느 책의 것인지는 변수 이름을 붙이는 습관에만 의존하게 된다.

동료가 이 코드를 가져다 쓰려 해도 어디까지 건드려도 되는지 알 수 없다. 대출 중 표시를 바꾸면서 대출 횟수를 올리는 일을 빠뜨리면 데이터가 어긋난다. 필요한 것은 책 한 권에 속한 값과 그 값을 바꾸는 동작을 한 덩어리로 묶고, 바깥에는 쓸 수 있는 동작의 목록만 보여 주는 방식이다. 그 덩어리가 클래스다. 이 장에서는 Book 과 Member 두 클래스를 만들어, 회원이 책을 빌리면 책의 상태가 바뀌는 흐름을 구현한다.

클래스는 헤더와 구현으로 나뉜다

@interface: 바깥에 보여 주는 약속

헤더 파일에는 @interface 블록을 쓴다. 클래스 이름, 부모 클래스, 바깥에서 보낼 수 있는 메시지의 목록이 여기에 들어간다. 부모 클래스(superclass)는 기능을 물려받는 상위 클래스이고, 이 책의 예제에서는 모두 Foundation의 기본 클래스인 NSObject 를 부모로 둔다. 객체를 만들고 메모리를 다루는 기본 기능이 NSObject 에 들어 있기 때문이다.

#import <Foundation/Foundation.h>

@interface Book : NSObject
- (NSString *)title;
- (BOOL)lend;
@end

첫 줄의 #import 는 C의 #include 와 비슷하지만 같은 파일을 여러 번 읽지 않도록 처리해 준다. 이 헤더를 읽는 쪽은 Book 에 title 과 lend 라는 메시지를 보낼 수 있다는 사실만 알면 된다. 값이 어떻게 저장되는지는 보이지 않아도 된다.

@implementation: 실제 동작

구현 파일에는 @implementation 블록을 쓰고 헤더에 선언한 메서드의 본문을 채운다. 구현 파일은 맨 위에서 자기 헤더를 #import 한다.

#import "Book.h"

@implementation Book
- (NSString *)title { return @"예시"; }
- (BOOL)lend { return YES; }
@end

#import 에서 꺾쇠(< >)는 시스템 헤더를, 큰따옴표는 우리 프로젝트의 헤더를 찾을 때 쓴다. 두 파일의 역할은 아래 표와 같다.

헤더와 구현 파일에 들어가는 내용
구분헤더 (.h)구현 (.m)읽는 쪽
블록@interface … @end@implementation … @end-
메서드선언만, 세미콜론으로 끝낸다선언과 같은 모양에 본문을 붙인다-
인스턴스 변수쓸 수 있으나 이 책에서는 쓰지 않는다블록 첫머리의 중괄호 안-
공개 범위이 파일을 import 한 모든 코드해당 클래스의 구현 안다른 파일은 .m 을 읽지 않는다
헤더는 구현 파일과 사용하는 쪽이 함께 import 하고, 두 .m 파일이 함께 컴파일되어 실행 파일 하나가 된다.

인스턴스 변수

객체 하나가 자기 몫으로 들고 있는 변수를 인스턴스 변수라 한다. Book 객체가 세 개면 _title 도 세 벌 따로 있다. 이 책에서는 인스턴스 변수를 @implementation 바로 뒤의 중괄호 안에 선언한다. 이렇게 하면 변수는 구현 파일 안에서만 보이고, 바깥은 메서드를 통해서만 값을 다룬다. 이름 앞에 밑줄(_)을 붙이는 것은 지역 변수나 인자와 구별하려는 Objective-C의 관례다.

@implementation Book {
    NSString *_title;
    NSInteger _loanCount;
    BOOL _onLoan;
}
@end

객체가 처음 만들어지면 인스턴스 변수는 모두 0으로 채워진다. 숫자는 0, BOOL 은 NO, 객체를 가리키는 포인터는 nil(아무것도 가리키지 않는 값)이다. 그래서 _loanCount 를 따로 0으로 맞추지 않아도 된다. 객체를 만들고 초기화하는 과정과 메모리 관리는 뒤에서 따로 다룬다. 이 장에서는 [[Book alloc] init] 으로 객체를 만든다고만 받아들이면 된다. 문자열을 담는 NSString * 변수는 ARC(Automatic Reference Counting, 자동 참조 계산)가 켜진 상태에서 대입하는 순간 객체를 붙잡아 두므로, 직접 해제할 필요가 없다.

변수를 바깥에 직접 열어 두지 않았으므로 값을 읽고 쓰는 메서드가 필요하다. 읽는 메서드(getter)는 변수와 같은 이름을 쓰고, 쓰는 메서드(setter)는 set 으로 시작하는 이름을 쓰는 것이 관례다. 이 일을 자동으로 해 주는 기능이 프로퍼티인데, 다음 장에서 다룬다. 지금은 직접 써 보면서 프로퍼티가 어떤 반복을 줄여 주는지 몸으로 확인한다.

메서드 선언과 self

선언을 읽는 법

메서드 선언은 C 함수와 모양이 많이 다르다. 맨 앞의 기호, 반환형, 이름과 인자가 번갈아 나오는 구조를 하나씩 보자.

- (void)setTitle:(NSString *)title author:(NSString *)author;
메서드 선언의 각 부분
조각예뜻
맨 앞 기호-인스턴스에 보내는 메서드. 클래스 자체에 보내는 메서드는 +
반환형(void)괄호 안에 쓴다. 돌려줄 값이 없으면 void
이름 조각과 인자setTitle:(NSString *)title콜론 뒤에 인자형과 인자 이름이 온다
다음 조각author:(NSString *)author인자마다 이름 조각이 붙는다

이 메서드의 전체 이름(selector)은 콜론까지 포함한 setTitle:author: 이다. 호출할 때는 [a setTitle:@"알고리즘 입문" author:@"김서연"] 처럼 조각마다 값을 붙여 쓰므로 어느 값이 무엇인지 읽기 쉽다. 구현은 선언과 같은 줄에 세미콜론 대신 중괄호 본문을 붙이면 된다. 선언과 구현의 모양이 다르면 컴파일러가 서로 다른 메서드로 본다.

self: 지금 메시지를 받은 객체

인스턴스 메서드 안에서 self 는 그 메서드를 실행하라는 메시지를 받은 객체를 가리킨다. [a summary] 를 보내면 summary 가 실행되는 동안 self 는 a 가 가리키는 객체다. 메서드 안에서 같은 객체의 다른 메서드를 부르고 싶을 때는 [self statusText] 처럼 self 에게 메시지를 보낸다. 앞 장에서 배운 대괄호 문법에서 받는 쪽 자리에 self 를 쓴 것뿐이다.

인스턴스 변수 _title 은 self 가 가리키는 객체의 칸이다. 그래서 같은 summary 코드를 책 a 와 책 b 에 각각 보내도 a 에는 a 의 제목이, b 에는 b 의 제목이 나온다. 코드는 클래스에 하나만 있고, 값은 객체마다 따로 있다는 점이 이 구조의 핵심이다.

메서드가 실행되는 동안 self 는 메시지를 받은 객체이고, 인스턴스 변수는 그 객체가 가진 칸이다.

self 를 쓸 때 주의할 점이 하나 있다. getter 안에서 [self title] 을 부르면 자기 자신을 다시 부르게 되어 끝없이 되풀이된다. 같은 객체의 값을 읽는 가장 짧은 길은 변수 이름을 그대로 쓰는 것이다. 뒤의 틀리기 쉬운 사례에서 다시 본다.

메서드를 구현하는 순서도 알아 둘 만하다. 같은 @implementation 안에서는 메서드를 부르는 쪽이 부르는 메서드보다 앞에 있어도 된다. C 함수처럼 앞에 미리 선언해 둘 필요가 없다. 헤더에 선언하지 않고 구현 파일에만 쓴 메서드는 그 클래스 안에서만 쓰는 보조 메서드가 된다. 이 장의 예제에서 statusText 가 그런 경우다.

여러 파일을 함께 컴파일하기

파일이 나뉘면 컴파일러가 각 .m 파일을 따로 번역해 목적 파일(object file)을 만들고, 링커(linker)가 이것들을 이어 붙여 실행 파일을 만든다. 헤더는 번역 대상이 아니라 #import 로 읽어 들이는 문서이므로 명령줄에 쓰지 않는다. 이 책의 표준 명령은 .m 파일을 모두 넘긴다.

clang -fobjc-arc -Wall -framework Foundation *.m -o app

-fobjc-arc 는 ARC를 켜고, -Wall 은 주요 경고를 켜며, -framework Foundation 은 Foundation 라이브러리를 이어 붙인다. 현재 디렉터리의 모든 .m 파일이 대상이므로 파일을 추가해도 명령은 바뀌지 않는다. 파일별로 나누어 번역하는 과정을 직접 보고 싶다면 -c 옵션을 쓴다.

clang -fobjc-arc -Wall -c Book.m
clang -fobjc-arc -Wall -c Member.m
clang -fobjc-arc -Wall -c main.m
clang -fobjc-arc -framework Foundation Book.o Member.o main.o -o app

-c 는 링크하지 않고 .o 파일까지만 만든다. 마지막 명령이 .o 파일들을 이어 붙인다. 한 파일만 고쳤을 때 그 파일만 다시 번역할 수 있다는 것이 나누어 컴파일하는 이점이다. 이 책에서는 편의상 한 줄 명령을 쓴다.

헤더 안에서 다른 클래스를 가리킬 때

Member 의 메서드는 Book 객체를 인자로 받는다. Member.h 에서 Book 이라는 이름만 필요하고 Book 의 메서드 목록까지는 필요하지 않으므로, 전체 헤더를 읽는 대신 @class Book; 한 줄로 이런 클래스가 있다고만 알린다. 이를 전방 선언(forward declaration)이라 한다. 실제로 Book 에 메시지를 보내는 Member.m 에서는 #import "Book.h" 를 쓴다. 헤더끼리 서로를 import 하면 순환이 생겨 컴파일이 막히는데, 전방 선언은 이를 피하는 방법이기도 하다.

결과 출력과 NSLog

Foundation 에는 NSLog 라는 출력 함수가 있다. 그러나 NSLog 는 줄마다 시간과 프로세스 정보를 앞에 붙이고, 표준 출력이 아닌 표준 오류로 내보낸다. 실행할 때마다 출력이 달라져 결과를 비교하기 어렵고, 파이프나 파일로 결과만 받으려 할 때도 걸림돌이 된다. 그래서 이 책의 예제는 C의 printf 에 문자열 객체의 UTF8String 을 넘겨 표준 출력에 쓴다. NSLog 는 개발 중 진단 메시지용으로 남겨 둔다.

완성 코드

세 파일을 같은 디렉터리에 두고 컴파일한다. 회원이 책을 빌리면 Member 가 Book 의 lend 를 호출하는 구조이고, 한 사람이 빌릴 수 있는 책은 두 권까지다.

Book.h

#import <Foundation/Foundation.h>

@interface Book : NSObject

- (void)setTitle:(NSString *)title author:(NSString *)author;
- (NSString *)title;
- (BOOL)isOnLoan;
- (NSInteger)loanCount;
- (BOOL)lend;
- (void)giveBack;
- (NSString *)summary;

@end

Book.m

#import "Book.h"

@implementation Book {
    NSString *_title;
    NSString *_author;
    NSInteger _loanCount;
    BOOL _onLoan;
}

- (void)setTitle:(NSString *)title author:(NSString *)author {
    _title = title;
    _author = author;
}

- (NSString *)title {
    return _title;
}

- (BOOL)isOnLoan {
    return _onLoan;
}

- (NSInteger)loanCount {
    return _loanCount;
}

- (BOOL)lend {
    if (_onLoan) {
        return NO;
    }
    _onLoan = YES;
    _loanCount += 1;
    return YES;
}

- (void)giveBack {
    _onLoan = NO;
}

- (NSString *)statusText {
    return _onLoan ? @"대출 중" : @"대출 가능";
}

- (NSString *)summary {
    return [NSString stringWithFormat:@"%@ / %@ [%@]",
            _title, _author, [self statusText]];
}

@end

Member.h

#import <Foundation/Foundation.h>

@class Book;

@interface Member : NSObject

- (void)setName:(NSString *)name;
- (NSString *)name;
- (NSInteger)borrowedCount;
- (BOOL)borrow:(Book *)book;

@end

Member.m

#import "Member.h"
#import "Book.h"

static const NSInteger kMaxBorrow = 2;

@implementation Member {
    NSString *_name;
    NSInteger _borrowedCount;
}

- (void)setName:(NSString *)name {
    _name = name;
}

- (NSString *)name {
    return _name;
}

- (NSInteger)borrowedCount {
    return _borrowedCount;
}

- (BOOL)borrow:(Book *)book {
    if (_borrowedCount >= kMaxBorrow) {
        return NO;
    }
    if (![book lend]) {
        return NO;
    }
    _borrowedCount += 1;
    return YES;
}

@end

main.m

#import <Foundation/Foundation.h>
#import "Book.h"
#import "Member.h"

static void show(NSString *line) {
    printf("%s\n", [line UTF8String]);
}

static void attempt(Member *member, Book *book) {
    BOOL ok = [member borrow:book];
    show([NSString stringWithFormat:@"%@ 대출 시도 - %@: %@",
          [member name], [book title], ok ? @"성공" : @"실패"]);
}

int main(void) {
    @autoreleasepool {
        Book *a = [[Book alloc] init];
        [a setTitle:@"알고리즘 입문" author:@"김서연"];
        Book *b = [[Book alloc] init];
        [b setTitle:@"데이터베이스 설계" author:@"박도윤"];
        Book *c = [[Book alloc] init];
        [c setTitle:@"네트워크 기초" author:@"이하준"];

        Member *minjun = [[Member alloc] init];
        [minjun setName:@"최민준"];
        Member *sua = [[Member alloc] init];
        [sua setName:@"정수아"];

        show([a summary]);
        show([b summary]);
        show([c summary]);

        attempt(minjun, a);
        attempt(sua, a);
        attempt(minjun, b);
        attempt(minjun, c);

        show([NSString stringWithFormat:@"%@의 대출 권수: %ld",
              [minjun name], (long)[minjun borrowedCount]]);
        show([a summary]);
        [a giveBack];
        show([a summary]);
        show([NSString stringWithFormat:@"%@ 누적 대출 횟수: %ld",
              [a title], (long)[a loanCount]]);
    }
    return 0;
}

줄별 해설

Book.h

  • #import 한 줄로 Foundation 을 읽는다. NSObject, NSString, NSInteger, BOOL 이 모두 여기서 온다.
  • @interface Book : NSObject 는 Book 이 NSObject 의 자식 클래스라는 뜻이다. 이 블록에는 변수를 쓰지 않았고 메서드 선언만 있다.
  • setTitle:author: 는 인자가 둘이라 이름 조각도 둘이다. isOnLoan 은 BOOL 을 돌려주는 메서드에 is 로 시작하는 이름을 붙인 것이다.
  • statusText 는 일부러 선언하지 않았다. 바깥에서 쓸 일이 없는 보조 메서드이기 때문이다.

Book.m

  • @implementation Book 뒤의 중괄호 안에 인스턴스 변수 네 개를 선언했다. 이 변수는 Book.m 안에서만 보인다.
  • setTitle:author: 는 인자로 받은 문자열을 인스턴스 변수에 대입한다. ARC 덕분에 대입된 문자열은 Book 객체가 살아 있는 동안 유지된다.
  • lend 는 이미 대출 중이면 NO 를 돌려주고 끝낸다. 그렇지 않으면 상태를 바꾸고 횟수를 하나 올린 뒤 YES 를 돌려준다. 상태 확인과 변경이 한 메서드에 모여 있어 둘 중 하나만 빠지는 일이 없다.
  • statusText 는 삼항 연산자로 문자열을 고른다. @"…" 는 NSString 객체를 만드는 리터럴이다.
  • summary 는 [self statusText] 로 같은 객체의 보조 메서드를 부른다. stringWithFormat: 에서 %@ 는 객체를 문자열로 바꿔 끼우는 자리 표시자다.

Member.h, Member.m

  • @class Book; 은 Book 이라는 이름만 미리 알린다. 이 헤더가 Book.h 를 읽지 않으므로 Member.h 를 import 하는 쪽의 컴파일이 가벼워진다.
  • Member.m 은 Book 에 메시지를 보내므로 Book.h 를 import 한다.
  • static const NSInteger kMaxBorrow 는 이 파일 안에서만 쓰는 상수다. 한도를 바꿀 때 이 줄만 고치면 된다.
  • borrow: 는 먼저 한도를 확인하고, 그다음 [book lend] 가 성공했을 때만 권수를 올린다. 책이 이미 나가 있으면 권수는 그대로다.

main.m

  • show 는 문자열 객체를 받아 printf 로 표준 출력에 쓰는 C 함수다. 앞에 static 을 붙여 이 파일 안에서만 쓰게 했다.
  • attempt 는 대출을 시도하고 결과 한 줄을 출력한다. 객체를 인자로 받을 때도 포인터(Member *)로 받는다.
  • @autoreleasepool 은 ARC 아래에서 임시로 만들어지는 객체를 정리하는 영역이다. 메모리 관리를 다루는 곳에서 자세히 설명한다.
  • [[Book alloc] init] 으로 책 세 권과 회원 두 명을 만든다. 각각 별개의 객체이므로 인스턴스 변수도 따로 있다.
  • attempt 네 번의 결과는 이렇다. 민준이 a 를 빌려 성공하고, 수아가 같은 a 를 빌리려다 실패한다. 민준이 b 를 빌려 성공한 뒤 세 번째 책 c 는 한도 두 권에 걸려 실패한다.
  • %ld 에는 long 이 필요하므로 NSInteger 를 (long) 으로 바꿔 넘겼다. 이렇게 해야 -Wall 아래에서 경고가 나오지 않는다.
  • 마지막에 giveBack 을 보내 a 를 반납하고, 요약과 누적 횟수를 출력한다. 반납해도 누적 횟수는 1로 남는다.

실행 결과

$ clang -fobjc-arc -Wall -framework Foundation *.m -o app
$ ./app
알고리즘 입문 / 김서연 [대출 가능]
데이터베이스 설계 / 박도윤 [대출 가능]
네트워크 기초 / 이하준 [대출 가능]
최민준 대출 시도 - 알고리즘 입문: 성공
정수아 대출 시도 - 알고리즘 입문: 실패
최민준 대출 시도 - 데이터베이스 설계: 성공
최민준 대출 시도 - 네트워크 기초: 실패
최민준의 대출 권수: 2
알고리즘 입문 / 김서연 [대출 중]
알고리즘 입문 / 김서연 [대출 가능]
알고리즘 입문 누적 대출 횟수: 1

실무에서 자주 틀리는 것

헤더에 선언하지 않은 메서드를 바깥에서 부른다

구현 파일에만 쓴 메서드를 다른 파일에서 부르면 컴파일러가 그 메시지를 모른다. ARC를 켠 상태에서는 경고가 아니라 오류다.

/* 틀린 코드: statusText 는 Book.h 에 없다 */
show([a statusText]);
/* error: no visible @interface for 'Book' declares the selector 'statusText' */
/* 고친 코드: 바깥에 열기로 했다면 Book.h 에 선언을 추가한다 */
- (NSString *)statusText;

열지 않기로 했다면 바깥에서 부르는 코드를 없애고, 이미 있는 summary 같은 공개 메서드를 쓴다.

.m 파일을 #import 한다

헤더 대신 구현 파일을 import 하면 같은 클래스 정의가 두 번 번역된다. *.m 으로 모두 컴파일하는 명령에서는 링크 단계에서 중복 기호 오류가 난다.

/* 틀린 코드: main.m */
#import "Book.m"
/* duplicate symbol '_OBJC_CLASS_$_Book' in: Book.o main.o */
/* 고친 코드: 헤더만 import 한다 */
#import "Book.h"

getter 안에서 self 로 자기 자신을 부른다

title 을 구현하면서 [self title] 을 돌려주면 title 이 title 을 부르고, 그 안에서 또 부르기를 되풀이한다. 컴파일은 되지만 실행하면 호출 스택이 가득 차 프로그램이 비정상 종료한다.

/* 틀린 코드 */
- (NSString *)title {
    return [self title];
}
/* 고친 코드: 변수를 직접 읽는다 */
- (NSString *)title {
    return _title;
}

선언만 하고 구현을 빠뜨린다

헤더에 giveBack 을 선언해 놓고 Book.m 에 본문을 쓰지 않으면 -Wall 에서 구현이 없다는 경고가 나온다. 경고를 넘기고 실행하면 그 메시지를 보내는 순간 프로그램이 멈춘다.

/* 틀린 코드: Book.h 에는 있고 Book.m 에는 없다 */
[a giveBack];
/* warning: method definition for 'giveBack' not found */
/* 실행 시: unrecognized selector sent to instance */
/* 고친 코드: Book.m 에 구현을 추가한다 */
- (void)giveBack {
    _onLoan = NO;
}

한눈에 보기

이 장에서 쓴 문법과 의미
문법위치의미
@interface 이름 : 부모 … @end.h바깥에 보이는 메서드 목록
@implementation 이름 { 변수 } … @end.m인스턴스 변수와 메서드 본문
- (반환형)이름:(형)인자.h, .m인스턴스 메서드 선언과 구현의 머리
_title.m 의 메서드 안self 가 가리키는 객체의 변수
[self 메시지]메서드 안같은 객체에 다른 메시지를 보낸다
@class 이름;.h이름만 미리 알리는 전방 선언
#import "이름.h".m, main.m사용할 클래스의 헤더를 읽는다
clang … *.m -o app터미널모든 .m 을 번역해 하나로 링크한다
자주 만나는 증상과 원인
증상원인확인할 곳
no visible @interface … selector헤더에 선언이 없다.h 의 메서드 목록
duplicate symbol.m 을 import 했다#import 줄
Undefined symbols … OBJC_CLASS그 클래스의 .m 을 명령줄에서 뺐다컴파일 명령
unrecognized selector선언만 있고 구현이 없다.m 의 @implementation

연습 문제

  1. Member 에 책을 돌려받는 메서드 giveBack: 을 추가하려 한다. 인자는 Book 객체이고, 대출 중인 책이면 반납시키고 대출 권수를 하나 줄인다. Member.h 와 Member.m 에 각각 무엇을 쓰는지 적어라.
  2. Member.h 에서 #import "Book.h" 대신 @class Book; 을 쓰는 이유를 두 가지 이상 설명하라.
  3. Book.m 이 없는 상태에서 clang -fobjc-arc -Wall -framework Foundation main.m Member.m -o app 을 실행하면 어떤 종류의 오류가 나는가. 컴파일 오류인지 링크 오류인지 밝히고 이유를 설명하라.
  4. Book 의 summary 안에서 [self statusText] 를 [self summary] 로 바꾸면 어떻게 되는가. 이유를 설명하라.

정답과 해설

  1. Member.h 에 선언을 추가한다.
    - (void)giveBack:(Book *)book;
    Member.m 에는 구현을 추가한다.
    - (void)giveBack:(Book *)book {
        if (_borrowedCount > 0 && [book isOnLoan]) {
            [book giveBack];
            _borrowedCount -= 1;
        }
    }
    Book 의 giveBack 과 이름이 같지만 받는 객체의 클래스가 다르고 콜론이 붙어 있어 별개의 메서드다. 이 구현은 이 회원이 그 책을 실제로 빌렸는지까지는 확인하지 않는다. 그것을 확인하려면 빌린 책의 목록을 보관해야 하고, 컬렉션을 배운 뒤에 가능하다.
  2. 첫째, Member.h 는 Book 이라는 이름만 필요하고 Book 의 메서드 목록은 필요하지 않다. 둘째, Member.h 를 import 한 모든 파일이 Book.h 까지 읽게 되는 연쇄를 막아 컴파일 의존이 줄어든다. 셋째, 나중에 Book.h 가 Member 를 필요로 하게 되어도 두 헤더가 서로를 import 하는 순환이 생기지 않는다. 실제로 메시지를 보내는 Member.m 에서는 Book.h 를 import 해야 한다.
  3. 링크 오류다. 각 파일은 헤더만으로 번역이 되므로 컴파일은 통과한다. 그러나 Book 의 실제 코드는 Book.m 에만 있어서, 링커가 이어 붙일 때 Book 클래스 기호를 찾지 못하고 Undefined symbols 오류를 낸다. 해결은 명령에 Book.m 을 포함하는 것이고, *.m 을 쓰면 빠뜨릴 일이 없다.
  4. summary 가 summary 를 다시 부르게 되어 같은 호출이 끝없이 되풀이된다. 컴파일은 되지만 실행하면 호출 스택이 가득 차 비정상 종료한다. self 는 같은 객체를 가리키므로 자기 자신을 부르는 메시지도 막힘 없이 전달되기 때문이다. 메서드가 자기 자신을 부를 때는 종료 조건이 있어야 한다.
오탈자·오류 제보 비공개로 접수되어 원고 수정에 반영됩니다

이메일 등 개인정보는 받지 않습니다. 답변이 필요한 질문은 아래 댓글을 이용해 주세요.

READER FEEDBACK

질문·의견

내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.