[objective-c] Objective-C에서 추상 클래스 작성

저는 원래 Objective-C와 함께 일하는 Java 프로그래머입니다. 추상 클래스를 만들고 싶지만 Objective-C에서는 불가능한 것으로 보입니다. 이것이 가능한가?

그렇지 않다면 Objective-C에서 추상 클래스에 얼마나 가까이 갈 수 있습니까?



답변

일반적으로 Objective-C 클래스는 규칙에 의해서만 추상화됩니다. 작성자가 클래스를 추상으로 문서화하는 경우 서브 클래 싱없이 사용하지 마십시오. 그러나 추상 클래스의 인스턴스화를 막는 컴파일 타임 적용은 없습니다. 실제로, 사용자가 카테고리를 통해 (즉, 런타임에) 추상 메소드의 구현을 제공하는 것을 막을 수있는 것은 없습니다. 추상 클래스의 메소드 구현에서 예외를 발생시켜 사용자가 최소한 특정 메소드를 대체하도록 할 수 있습니다.

[NSException raise:NSInternalInconsistencyException
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

메서드가 값을 반환하면 사용하기가 조금 더 쉽습니다.

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

그런 다음 메소드에서 return 문을 추가 할 필요가 없습니다.

추상 클래스가 실제로 인터페이스 인 경우 (즉, 구체적인 메소드 구현이없는 경우) Objective-C 프로토콜을 사용하는 것이 더 적합한 옵션입니다.


답변

아니요, Objective-C에서 추상 클래스를 만들 수있는 방법이 없습니다.

메소드 / 선택자가 doesNotRecognizeSelector를 호출하여 추상 클래스를 조롱 할 수 있으므로 클래스를 사용할 수 없게하는 예외가 발생합니다.

예를 들면 다음과 같습니다.

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

초기화를 위해이 작업을 수행 할 수도 있습니다.


답변

위의 @Barry Wark의 답변을 riffing하고 iOS 4.3으로 업데이트하면 내 참조로 남겨 둘 수 있습니다.

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

그런 다음 방법으로 이것을 사용할 수 있습니다

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}

참고 : 매크로를 C 함수처럼 보이게 만드는 것이 좋은지 아닌지는 확실하지 않지만 반대로 학습 할 때까지 계속 유지합니다. 런타임 시스템 이 호출 에 응답하여 던지기 때문에 NSInvalidArgumentException(문서 대신 NSInternalInconsistencyException) 사용하는 것이 더 정확하다고 생각합니다 doesNotRecognizeSelector( NSObject문서 참조 ).


답변

내가 생각해 낸 해결책은 다음과 같습니다.

  1. “추상”클래스에서 원하는 모든 것에 대한 프로토콜을 만듭니다.
  2. 프로토콜을 구현하는 기본 클래스 (또는 추상이라고도 함)를 작성하십시오. “abstract”를 원하는 모든 메소드에 대해 .h 파일이 아닌 .m 파일로 구현하십시오.
  3. 자녀 클래스가 기본 클래스에서 상속하고 프로토콜을 구현하도록하십시오.

이런 식으로 컴파일러는 자식 클래스에 의해 구현되지 않은 프로토콜의 모든 메소드에 대해 경고합니다.

Java처럼 간결하지는 않지만 원하는 컴파일러 경고가 표시됩니다.


답변

로부터 옴니 그룹 메일 링리스트 :

현재 Objective-C에는 Java와 같은 추상 컴파일러 구성이 없습니다.

따라서 추상 클래스를 다른 일반 클래스로 정의하고 비어 있거나 선택기를 지원하지 않는 추상 메소드에 대한 메소드 스텁을 구현하기 만하면됩니다. 예를 들어 …

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

또한 기본 이니셜 라이저를 통해 추상 클래스가 초기화되지 않도록 다음을 수행합니다.

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}


답변

추상 기본 클래스를 작성하는 대신 프로토콜 (Java 인터페이스와 유사) 사용을 고려하십시오. 이를 통해 일련의 메소드를 정의한 후 프로토콜을 준수하는 모든 오브젝트를 승인하고 메소드를 구현할 수 있습니다. 예를 들어 Operation 프로토콜을 정의한 후 다음과 같은 기능을 수행 할 수 있습니다.

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

여기서 op는 작업 프로토콜을 구현하는 모든 개체가 될 수 있습니다.

단순히 메소드를 정의하는 것 이상을 수행하기 위해 추상 기본 클래스가 필요한 경우 일반 Objective-C 클래스를 작성하여 인스턴스화되지 않도록 할 수 있습니다. -(id) init 함수를 대체하고 nil 또는 assert (false)를 리턴하도록하십시오. 매우 깨끗한 솔루션은 아니지만 Objective-C는 완전히 동적이기 때문에 추상 기본 클래스와 직접적으로 동등한 것은 없습니다.


답변

이 스레드는 오래되었으며 공유하고 싶은 대부분은 이미 여기에 있습니다.

그러나 내가 가장 좋아하는 방법은 언급되어 있지 않으며 AFAIK는 현재 Clang에서 기본 지원이 없으므로 여기로 이동하십시오.

첫째, 다른 사람들이 이미 지적했듯이 추상 클래스는 Objective-C에서 매우 드문 일입니다. 우리는 대개 컴포지션 (때로는 위임을 통해)을 사용합니다. 이것은 아마도 이러한 기능이 언어 / 컴파일러에 존재하지 않는 이유 일 것입니다. @dynamic속성 과는 별도로 IIRC가 CoreData의 도입과 함께 ObjC 2.0에 추가되었습니다.

그러나 위임 (또는 일반적으로 조성물) 당신의 문제를 해결하는 데 적합하지 않습니다 있다는 결론에 도달했다 (! 상황의주의 깊은 평가 후) 여기 방법은 주어진 나는 그것을 할 :

  1. 기본 클래스에서 모든 추상 메소드를 구현하십시오.
  2. 구현하십시오 [self doesNotRecognizeSelector:_cmd];
  3. __builtin_unreachable();공백하지 않은 방법에 대해 경고 메시지를 끄면“반환없이 비 공백 기능의 끝에 도달했다는 제어”를 알려줍니다.
  4. 매크로에서 2 단계와 3 단계를 결합하거나 구현없이 카테고리에서 -[NSObject doesNotRecognizeSelector:]사용하여 주석 을 달아 해당 메소드의 원래 구현을 대체하지 말고 해당 카테고리의 헤더를 프로젝트 PCH에 포함 시키십시오.__attribute__((__noreturn__))

필자는 개인적으로 상용구를 최대한 줄일 수있는 매크로 버전을 선호합니다.

여기있어:

// Definition:
#define D12_ABSTRACT_METHOD {\
 [self doesNotRecognizeSelector:_cmd]; \
 __builtin_unreachable(); \
}

// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString

#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD

#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
    if (aRange.location + aRange.length >= [self length])
        [NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];

    unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
    [self getCharacters:buffer range:aRange];

    return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…

@end

보시다시피, 매크로는 추상 메소드의 전체 구현을 제공하여 보일러 플레이트의 필요한 양을 최소한으로 줄입니다.

더 나은 옵션은 Clang 팀 에 기능 요청을 통해이 경우에 대한 컴파일러 속성을 제공 하도록 로비하는 것 입니다. (이것은 NSIncrementalStore와 같은 서브 클래스 시나리오에서 컴파일 타임 진단을 가능하게하기 때문에 더 좋습니다.)

이 방법을 선택하는 이유

  1. 효율적이고 다소 편리하게 작업이 완료됩니다.
  2. 이해하기 쉽습니다. (좋아요, __builtin_unreachable()사람들을 놀라게 할 수도 있지만 이해하기도 쉽습니다.)
  3. 어설 션 매크로 중 하나를 기반으로하는 접근 방식과 달리 다른 컴파일러 경고 나 오류를 생성하지 않으면 릴리스 빌드에서 제거 할 수 없습니다.

마지막 요점은 약간의 설명이 필요합니다.

릴리스 빌드에서 일부 (가장?) 사람들이 어설 션을 제거합니다. (나는 그 습관에 동의하지 않지만, 그것은 또 다른 이야기이다 …) 그러나 필요한 방법을 구현하지 못하는 것은 – 나쁜 , 끔찍한 , 잘못된 , 그리고 기본적으로 당신의 프로그램을위한 우주의 끝이다 . 프로그램이 정의되지 않았기 때문에 프로그램이 이와 관련하여 올바르게 작동 할 수 없으며 정의되지 않은 동작이 최악입니다. 따라서 새로운 진단을 생성하지 않고 해당 진단을 제거 할 수 있으면 완전히 수용 할 수 없습니다.

그러한 프로그래머 오류에 대해 적절한 컴파일 타임 진단을 얻을 수 없으며 런타임 오류 발견에 의존해야하지만 릴리스 빌드에서 오류를 해결할 수 있다면 왜 추상 클래스를 사용해보십시오. 처음?