제3장 인터페이스와 API 디자인 - 제17조: description 실현 방법

6661 단어
프로그램을 테스트할 때, 자주 대상 정보를 인쇄하고 보아야 한다.코드를 작성해서 대상의 모든 속성을 로그에 출력하는 방법이 있다.그러나 가장 자주 사용하는 방법은 다음과 같다.
NSLog(@"object = %@", object);

로그에 인쇄할 문자열을 만들 때, Object 대상은 description 메시지를 받습니다. 이 방법으로 되돌아오는 설명 정보는 '형식 문자열' (format string) 의 '% @' 을 대체합니다.예를 들어, Object는 다음 코드로 정보를 인쇄할 수 있는 배열입니다.
NSArray *object = @[@"A string", @(123)];
NSLog(@"object = %@", object);

출력:
object = (
     "A string",
     123
)

그러나 사용자 정의 클래스에서 이렇게 하면 출력된 정보는 다음과 같다.
object = 

object가 수조로 되어 있을 때 출력하는 정보와 비교하면 위의 이런 내용은 그다지 유용하지 않다.자신의 클래스에 description 방법을 덮어쓰지 않으면, 정보를 인쇄할 때 NSObject 클래스가 실행하는 기본 방법을 사용합니다.이 방법은 NSObject 프로토콜에 정의되어 있지만 NSObject 클래스도 이를 실현했다.NSObject가 유일한 '루트 클래스' 가 아니기 때문에 많은 방법들이 NSObject 프로토콜에 정의되어야 한다.예를 들어 NSProxy도 NSObject 프로토콜을 준수한 '루트 클래스' 이다.description 등 방법은 NSObject 프로토콜에 정의되어 있기 때문에 NSProxy와 같은 '루트 클래스' 와 하위 클래스도 반드시 그것들을 실현해야 한다.앞에서 보듯이 이러한 실현된 방법은 비교적 유용한 내용을 출력하지 않았고 클래스 이름과 대상의 메모리 주소만 출력했다.두 바늘이 정말 같은 대상을 가리키는지 판단하고 싶을 때만 이런 정보는 쓸모가 있다.그 외에는 더 이상 유용한 내용이 보이지 않는다.우리가 인쇄하고 싶은 대상 정보는 이것보다 더 많아야 한다.더 유용한 정보를 출력하려면 description 방법을 덮어쓰고 대상을 설명하는 문자열을 되돌려주면 됩니다.예를 들어 다음과 같은 개인정보를 나타내는 클래스가 있습니다.
#import 

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;

- (id)initWithFirstName:(NSString *)firstName
                       lastName:(NSString *)lastName;
@end

@implementation EOCPerson
- (id)initWithFirstName:(NSString *)firstName
                       lastName:(NSString *)lastName
{
        if ((self == [super init])) {
              _firstName = [firstName copy];
              _lastName = [lastName copy];
        }
        return self;
}
@end

일반적으로 이 클래스의 description 메서드는 다음과 같습니다.
- (NSString*)description {
    return [NSString stringWithFormat:@"", 
            [self class], self, _firstName, _lastName];
}

위의 코드로 작성한 경우 EOCPerson 객체는 다음 형식의 정보를 출력합니다.
EOCPerson *person = [[EOCPerson alloc] initWithFirstName: @"Bob" lastName:@"Smith"];
NSLog(@"person = %@", person);
//Output:
//person = 

이렇게 하면 이전에 출력한 정보를 복사하는 것보다 더욱 명확하고 유용하다.필자는 새로 실현된 description 방법에서도 기본적인 실현처럼 클래스의 이름과 바늘 주소를 출력해야 한다고 제안했다. 왜냐하면 이런 내용은 때때로 사용될 수 있기 때문이다.하지만 방금 보시다시피 NSArray 클래스의 대상은 이 두 가지 내용을 인쇄하지 않았습니다.분명히 description 방법을 실현할 때 고정된 규칙이 없기 때문에 현재 대상에 따라 description 방법에 어떤 정보를 인쇄할지 결정해야 한다.간단한 방법은 description에서 서로 다른 정보를 많이 출력할 수 있는데 그것이 바로 NSDictionary류의 description 방법을 빌리는 것이다.이 방법으로 내보낸 정보의 형식은 다음과 같습니다.
{
      key: value;
      foo: bar;
}

사용자 정의 description 방법에서 인쇄할 정보를 사전에 넣고 사전 대상의 description 방법이 출력한 내용을 문자열에 포함하여 되돌려주면 간략한 정보 출력 방식을 실현할 수 있습니다.예를 들어, 다음 클래스는 위치의 이름과 지리적 좌표(위도 및 경도)를 나타냅니다.
#import 

@interface EOCLocation : NSObject
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, assign, readonly) float latitude;
@property (nonatomic, assign, readonly) float longitude;
- (id)initWithTitle:(NSString*)title latitude:(float)latitude longitude:(float)longitude;
@end

@implementation EOCLocation
- (id)initWithTitle:(NSString*)title latitude:(float)latitude longitude:(float)longitude {
    if ((self = [super init])) {
        _title = [title copy];
        _latitude = latitude;
        _longitude = longitude;
    }
    return self;
}
@end

만약 이 종류의 description 방법이 지명과 경위도를 인쇄할 수 있다면 좋겠다.NSDictionary를 사용하여 다음과 같이 description 방법을 작성할 수 있습니다.
- (NSString*)description {
    return [NSString stringWithFormat:@"",
            [self class], 
            self, 
            @{@"title":_title, 
              @"latitude":@(_latitude), 
              @"longitude":@(_longitude)}
           ];
}

내보낸 정보의 형식은 다음과 같습니다.
location = 

이것은 바늘과 클래스 이름만 출력하는 것보다 훨씬 유용하며, 대상의 모든 속성을 잘 인쇄할 수 있다.또한 형식 문자열에서 모든 실례 변수의 위치를 직접 남겨서 하나씩 출력할 수도 있지만, NSDictionary로 이 기능을 실현하면 코드를 더욱 쉽게 유지할 수 있다. 만약 나중에 클래스에 속성을 추가하고 description 방법에서 인쇄하려면 사전 내용을 수정하기만 하면 된다.NSObject 프로토콜에서 주의해야 할 방법은 debugDescription입니다. 이 방법의 의도는 description과 매우 비슷합니다.양자의 차이점은 debugDescription 방법은 개발자가 디버거(debugger)에서 컨트롤러 명령으로 대상을 인쇄할 때 호출된다는 것이다.NSObject 클래스의 기본 구현에서 이 메서드는 description만 직접 호출합니다.EOCPerson 클래스의 경우 인스턴스를 만드는 데 사용되는 코드 뒤에 중단점을 삽입한 다음 디버거(LLDB를 사용하는 경우)를 통해 프로그램을 실행하여 중단합니다.
EOCPerson *person = [[EOCPerson alloc] initWithFirstName:@"Bob" lastName:@"Smith"];
NSLog(@"person = %@", person);
// Breakpoint here

프로그램이 인터럽트까지 실행될 때 개발자는 디버그 컨트롤러에 명령을 입력할 수 있다.LLDB의'po'명령은 다음과 같이 객체 인쇄(print-object) 작업을 완료합니다.
EOCTest[640:c07] person = (lldb) po person
(EOCPerson *) $1 = 0x0712a4d0 

콘솔의 "(EOCPerson*)$1=0x712a4d0"은 디버거에 의해 추가되고 그 다음 내용은 debugDescription에 의해 되돌아오는 정보입니다.EOCPerson 대상의 일반적인 설명 정보에만 사람의 이름을 넣고 디버깅에 사용되는 설명 정보에 더 자세한 내용을 넣고 싶을 수도 있습니다. 이 때 다음 코드로 이 두 가지 방법을 실현할 수 있습니다.
- (NSString*)description {
    return [NSString stringWithFormat:@"%@ %@", _firstName, _lastName];
}

- (NSString*)debugDescription {
    return [NSString stringWithFormat:@"", 
            [self class], self, _firstName, _lastName];
}

작성한 후에 아까 프로그램 코드를 다시 한 번 실행하면 이번 포 명령에서 출력된 대상 정보는 다음과 같다.
EOCTest[640:c07] person = Bob Smith
(lldb) po person
(EOCPerson *)$1 = 0x07117fb0

클래스 이름과 포인터 주소 같은 추가 내용을 일반적인 설명 정보에 넣고 싶지 않을 수도 있지만, 디버깅을 할 때 쉽게 볼 수 있기를 바랍니다. 이 경우 이러한 출력 방식을 사용할 수 있습니다.Foundation 프레임워크의 NSArray 클래스는 이렇게 합니다.예를 들면 다음과 같습니다.
NSArray *array = @[@"Effective Objective-C 2.0", @(123), @(YES)];
NSLog(@"array = %@", array);
// Breakpoint here

상기 프로그램 코드를 실행하고 단점에 멈춘 다음po 명령으로 그룹 대상을 인쇄하면 다음과 같은 정보를 볼 수 있습니다.
EOCTest[713:c07] array = (
      "Effective Objective-C 2.0",
      123,
       1
)
(lldb) po array
(NSArray *)$1 = 0x071275b0 <_nsarrayi>(
Effective Objective-C 2.0,
123,
1
)

이 실례를 설명하기 위해 description 방법을 실현하는 데 필요한 의미 있는 문자열을 되돌려줍니다.디버그할 때 더 자세한 대상 설명 정보를 출력하려면 debugDescription 방법을 실행해야 합니다.

좋은 웹페이지 즐겨찾기