제3장 인터페이스와 API 디자인 - 제16조:'완전한 초기화 방법'제공

8869 단어
모든 객체를 초기화합니다.초기화할 때, 일부 대상은 개발자가 그에게 추가 정보를 제공할 필요가 없을 수도 있지만, 일반적으로 그래도 제공해야 한다.일반적으로 대상은 필요한 정보를 모르면 작업을 완성할 수 없다.iOS의 UI 프레임워크인 UIKit의 경우 UItable ViewCell이라는 클래스가 있는데, 이 클래스 객체를 초기화할 때 유형별 셀을 구분할 수 있는 스타일과 식별자를 지정해야 합니다.이러한 객체는 작성 비용이 많이 들기 때문에 테이블을 그릴 때 식별자에 따라 재사용할 수 있으므로 프로그램 효율성이 향상됩니다.우리는 이 작업을 완성할 수 있도록 필요한 정보를 제공할 수 있는 초기화 방법을'전능 초기화 방법'(디자인nated initializer)이라고 부른다.만약 클래스 실례를 만드는 방식이 한 가지가 아니라면, 이 클래스는 여러 가지 초기화 방법이 있을 것이다.물론 좋지만, 그 중에서 전능 초기화 방법으로 다른 초기화 방법을 모두 사용하도록 선택해야 한다.NSDate 는 다음과 같은 방법으로 초기화됩니다.
- (id)init;
- (id)initWithString: (NSString *)string;
- (id)initWithTimeIntervalSinceNow: (NSTimeInterval)seconds;
- (id)initWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)refDate;
- (id)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds;
- (id)initWithTimeIntervalSince1970:(NSTimeInterval)seconds;

이 종류의 문서에서 말한 바와 같이, 위의 몇 가지 초기화 방법 중, "initWith Time Interval Since Reference Date:"는 전면적인 초기화 방법이다.나머지 초기화 방법은 모두 그것을 사용해야 한다는 것이다.따라서 전체 초기화 방법에서만 내부 데이터를 저장할 수 있다.이렇게 하면 베이스 데이터 저장 메커니즘이 바뀔 때 이 방법의 코드만 수정하면 되고 다른 초기화 방법을 변경할 필요가 없다.예를 들어 직사각형을 나타내는 클래스를 만들어야 한다.인터페이스는 다음과 같이 쓸 수 있습니다.
#import 

@interface EOCRectangle : NSObject
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;
@end

제18조의 건의에 따라 우리는 속성을 읽기 전용으로 성명한다.그러나 이렇게 되면 외부에서 Rectangle 대상의 속성을 설정할 수 없다.개발자는 두 속성을 설정하는 초기화 방법을 제공할 수 있습니다.
- (id)initWithWidth:(float)width 
          andHeight:(float)height
{
    if ((self = [super init])) {
        _width = width;
        _height = height;
    }
    return self;
}

그런데 만약에 직사각형을 [[EOCRectangle alloc] init]로 만드는 사람이 있다면 어떨까요?이렇게 하는 것은 규칙에 부합된다. 왜냐하면 EOCRectangle의 초유형 NSObject는 이 init라는 방법을 실현했고, 이 방법을 호출한 후 모든 실례 변수는 0으로 설정될 것이다. (또는 데이터 형식에 부합되고 0과 같은 값으로 설정될 것이다.)만약alloc 방법으로 분배된EOCRectangle를 이 방법으로 초기화한다면 직사각형의 너비와 높이는 0이다. 왜냐하면 모든 실례 변수가 0으로 설정되었기 때문이다.이것이 바로 당신이 원하는 결과일 수도 있지만, 이럴 때 우리는 기본적인 너비와 높이를 스스로 설정하거나 이상을 던지기를 원한다. 이 실례는 반드시 '전능 초기화 방법' 으로 초기화해야 한다는 것을 가리킨다.즉, EOCRectangle의 예에서 다음과 같이 그 중의 한 버전을 참조하여 init 방법을 복작해야 한다.
//Using default values
- (id)init {
    return [self initWithWidth:5.0f andHeight:10.0f];
}

//Throwing an exception
- (id)init {
      @throw [NSException exceptionWithName: NSInternalInconsistencyException reason: @"Must use initWithWidth: andHeight: instead." userInfo: nil];
}

기본값을 설정하는 init 방법은 모든 초기화 방법을 사용합니다.만약 이 버전으로 복사한다면 코드에 직접 설정할 수도 있습니다width와height 실례 변수의 값입니다.그러나 클래스의 밑바닥 저장 방식이 바뀌면 (예를 들어 개발자가 너비와 높이를 어떤 구조체에 함께 두기로 결정한 경우) init와 전능 초기화 방법으로 데이터를 설정하는 데 사용하는 코드를 모두 수정해야 한다.이 예와 같은 간단한 상황에서 큰 문제가 없지만 클래스의 초기화 방법은 여러 가지가 있고 초기화를 기다리는 데이터도 비교적 복잡하면 이렇게 하면 훨씬 번거롭다.그 중 어떤 초기화 방법을 수정하는 것을 잊어버리기 쉬워서 각 초기화 방법 간에 서로 일치하지 않게 된다.이제 EOCSquare라는 클래스를 만들어서 EOCRectangle의 하위 클래스로 만들어야 한다고 가정합니다.이런 계승 방식은 완전히 합리적이지만, 새로운 종류의 초기화 방법은 어떻게 써야 합니까?이 종류는 정사각형을 표시하기 때문에 그 너비와 높이는 반드시 같아야 한다.따라서 다음과 같이 초기화 방법을 만들 수 있습니다.
#import "EOCRectangle.h"

@interface EOCSquare : EOCRectangle
- (id)initWithDimension:(float)dimension;
@end

@implementation EOCSquare

- (id)initWithDimension:(float)dimension {
    return [super initWithWidth:dimension andHeight:dimension];
}

@end

상술한 방법은 바로 EOCSquare류의 전능 초기화 방법이다.이것은 초종류의 전능 초기화 방법을 사용했으니 주의하십시오.EOCRectangle 클래스의 실현 코드를 돌이켜 보면 그 클래스도 초기화 방법을 사용했다는 것을 알 수 있다.전능 초기화 방법의 호출 체인은 반드시 유지해야 한다.그러나 호출자는 "initWithWidth:andHeight:"또는 init 방법으로 EOCSquare 대상을 초기화할 수 있습니다.클래스의 작성자는 이런 상황을 보고 싶지 않습니다. 왜냐하면 이렇게 하면 '너비' 와 '높이' 가 같지 않은 정사각형을 만들 수 있기 때문입니다.따라서 클래스 계승을 할 때 주의해야 할 중요한 문제를 제시했다. 만약에 자류의 전능 초기화 방법과 초류 방법의 명칭이 다르면 항상 초류의 전능 초기화 방법을 복창해야 한다.EOCSquare라는 예에서 다음과 같이 EOCRectangle의 전능 초기화 방법을 덮어써야 한다.
- (id)initWithWidth:(float)width andHeight:(float)height {
    float dimension = MAX(width, height);
    return [self initWithDimension:dimension];
}

이 방법은 EOCSquare의 완전한 초기화 방법을 어떻게 이용하여 대상의 속성을 정확하게 확보하는지 주의하십시오.이 방법을 복기한 후에도 init를 사용하여 EOCSquare 대상을 초기화해도 평상시와 같이 일할 수 있다.EOCRectangle 클래스가 init 방법을 덮어쓰고 기본값을 매개 변수로 하여 이 클래스의 전체 초기화 방법을 호출했기 때문이다.init 방법으로 EOCSquare 대상을 초기화할 때도 이렇게 호출됩니다. 그러나 'init With Width: and Height:' 는 이미 하위 클래스에 복제되어 있기 때문에 실제로는 EOCSquare 클래스의 이 실현 코드를 실행하고 이 코드는 본 클래스의 전능 초기화 방법을 호출합니다.따라서 모든 것이 정상적이기 때문에 호출자는 가장자리 길이가 같지 않은 EOCSquare 대상을 만들 수 없다.때때로 우리는 초유의 전능 초기화 방법을 복제하고 싶지 않다. 왜냐하면 그렇게 하는 것은 이치에 맞지 않기 때문이다.예를 들어, 지금은 'init With Width: and Height:' 방법으로 두 파라미터 중 비교적 큰 자를 변장으로 해서 EOCSquare 대상을 초기화하고 싶지 않다.반대로 우리는 이것이 방법 호출자가 스스로 잘못을 저질렀다고 생각한다.이런 상황에서 자주 사용하는 방법은 초류의 전능 초기화 방법을 복제하고 그 중에서 이상을 던지는 것이다.
- (id)initWithWidth:(float)width andHeight:(float)height {
    @throw [NSException
        exceptionWithName:NSInternalInconsistencyException
                   reason:@"Must use initWithDimension: instead."
                 userInfo:nil];
}

이렇게 하면 보기에는 갑작스러워 보이지만, 때로는 필수적이다. 왜냐하면 그런 상황에서 만들어진 대상은 내부 데이터가 서로 일치하지 않을 수 있기 때문이다. (inconsistentinternal 데이터)만약 이렇게 한다면 EOC Rectangle와 EOC Square라는 예에서 init 방법을 사용하는 것도 이상을 던질 것이다. 왜냐하면 init 방법도'init With Width: and Height:'를 사용해야 하기 때문이다.이 때 init 메서드를 덮어쓰고 합리적인 기본값으로 initWithDimension: 메서드를 호출할 수 있습니다.
- (id)init {
    return [self initWithDimension:5.0f];
}

그러나 Objective-C 프로그램에서 심각한 오류가 발생할 때만 이상을 던져야 한다(제21조 참조). 따라서 초기화 방법에서 이상을 던지는 것은 부득이한 행동으로 실례가 정말 초기화할 수 없다는 것을 나타낸다.때때로 여러 개의 전능 초기화 방법을 작성해야 할 수도 있다.예를 들어 어떤 대상의 실례가 두 가지 완전히 다른 창설 방식이 있다면 반드시 분리해서 처리해야 한다. 그러면 이런 상황이 발생할 것이다.NSCoding 프로토콜의 경우 이 프로토콜은 자체 인코딩(encode) 및 디코딩(decode) 방식을 나타내는'시리얼화 메커니즘'(serialization mechanism)을 제공합니다.Mac OS X의 AppKit와 iOS의 UIKit 두 UI 프레임워크는 모두 이 메커니즘을 광범위하게 활용하여 대상을 서열화하여 XML 형식의'NIB'파일에 저장한다.이 NIB 파일들은 보기 컨트롤러 (view controller) 와 보기 레이아웃을 저장하는 데 사용됩니다.NIB 파일을 로드하면 시스템이 압축을 푸는 동안 뷰 컨트롤러를 디코딩합니다.NSCoding 프로토콜은 다음과 같은 초기화 방법을 정의하고 이를 준수하는 모든 사용자가 이를 수행해야 합니다.
- (id)initWithCoder:(NSCoder *)decoder;

우리는 이 방법을 실현할 때 일반적으로 평상시에 사용하는 그 전능 초기화 방법을 사용하지 않는다. 왜냐하면 이 방법은 디코더 (decoder) 를 통해 대상 데이터를 압축해야 하기 때문에 일반적인 초기화 방법과 다르다.그리고 만약에 슈퍼클래스도 NSCoding을 실현한다면 슈퍼클래스의'initWithCoder:'방법을 호출해야 한다.그래서 서브클래스 중 하나만이 초클래스의 초기화 방법을 호출한 것이 아니기 때문에 엄밀히 말하면 이런 상황에서 두 가지 전능 초기화 방법이 나타났다.EOCRectangle의 경우 코드는 다음과 같습니다.
#import 

@interface EOCRectangle : NSObject 
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;
- (id)initWithWidth:(float)width 
          andHeight:(float)height;
@end

@implementation EOCRectangle

// Designated initialiser
- (id)initWithWidth:(float)width 
          andHeight:(float)height
{
    if ((self = [super init])) {
        _width = width;
        _height = height;
    }
    return self;
}

// Super-class’s designated initialiser
- (id)init {
    return [self initWithWidth:5.0f andHeight:10.0f];
}

// Initialiser from NSCoding
- (id)initWithCoder:(NSCoder*)decoder {
    // Call through to super’s designated initialiser
    if ((self = [super init])) {
        _width = [decoder decodeFloatForKey:@"width"];
        _height = [decoder decodeFloatForKey:@"height"];
    }
}

@end

NSCoding 프로토콜의 초기화 방법은 이 클래스의 완전한 초기화 방법이 아니라 클래스 초과와 관련된 방법이 호출되었음을 주의하십시오.그러나 하이퍼클래스도 NSCoding을 실현하면 하이퍼클래스를 호출하는 'initWithCoder:' 초기화 방법으로 바꿔야 한다.예를 들어, 이 경우 EOCSquare 클래스는 다음과 같이 써야 합니다.
#import "EOCRectangle.h"

@interface EOCSquare : EOCRectangle
- (id)initWithDimension:(float)dimension;
@end

@implementation EOCSquare

// Designated initialiser
- (id)initWithDimension:(float)dimension {
    return [super initWithWidth:dimension andHeight:dimension];
}

// Super class designated initialiser
- (id)initWithWidth:(float)width andHeight:(float)height {
    float dimension = MAX(width, height);
    return [self initWithDimension:dimension];
}

// NSCoding designated initialiser
- (id)initWithCoder:(NSCoder*)decoder {
    if ((self = [super initWithCoder:decoder])) {
        // EOCSquare’s specific initialiser
    }
}

@end

모든 하위 클래스의 전능 초기화 방법은 슈퍼 클래스에 대응하는 방법을 호출하고 층층이 위로 올라가야 한다.'initWithCoder:'를 실현할 때도 이렇게 해야 한다. 먼저 슈퍼 클래스와 관련된 방법을 호출한 다음에 본 클래스와 관련된 작업을 수행해야 한다.이렇게 작성된 EOCSquare 클래스는 NSCoding 프로토콜을 완전히 준수한다(fully NSCoding compliant)."initWithCoder:"방법을 작성할 때 슈퍼클래스의 동명 방법을 사용하지 않고 자체적으로 초기화하는 방법이나 슈퍼클래스의 다른 초기화 방법을 호출하면 EOCRectangle 클래스의 "initWithCoder:"방법은 실행할 기회가 없기 때문에width 및Height 이 두 실례 변수는 디코딩되었다.
요점은 클래스에서 완전한 초기화 방법을 제공하고 문서에 표시합니다.다른 초기화 방법은 모두 이 방법을 사용해야 한다.
4
  • 만약에 전능 초기화 방법이 초류와 다르면 초류 중의 대응 방법을 복제해야 한다

  • 4
  • 만약에 슈퍼클래스의 초기화 방법이 서브클래스에 적용되지 않는다면 이 슈퍼클래스 방법을 덮어쓰고 그 중에서 이상을 던져야 한다
  • 좋은 웹페이지 즐겨찾기