Objective-C에서 추상 클래스 작성


507

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

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


18
아래 답변이 훌륭합니다. 필자는 추상 클래스의 문제가 전용 메소드와 접선 적으로 관련되어 있음을 발견했다. 둘 다 클라이언트 코드가 수행 할 수있는 작업을 제한하는 메소드이며 Objective-C에는 존재하지 않는다. 언어 자체의 사고 방식이 Java와 근본적으로 다르다는 것을 이해하는 것이 도움이된다고 생각합니다. 내 답변보기 : stackoverflow.com/questions/1020070/#1020330
Quinn Taylor

다른 언어와 달리 Objective-C 커뮤니티의 정신에 관한 정보에 감사드립니다. 그것은 실제로 내가 가지고있는 많은 관련 질문을 해결합니다 (개인 메서드에 대한 간단한 메커니즘이없는 이유 등).
Jonathan Arbogast

1
CocoaDev 사이트를 살펴보면

2
Barry는 그것을 나중에 생각으로 언급하지만 (내가 잘못 읽고 있다면 용서하십시오), 나는 당신이 Objective C에서 프로토콜 을 찾고 있다고 생각합니다 . 예를 들어, 프로토콜은 무엇입니까? .
jww

답변:


633

일반적으로 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 프로토콜을 사용하는 것이 더 적합한 옵션입니다.


18
대답의 가장 적절한 부분은 메소드를 정의하기 위해 @protocol을 사용할 수 있다고 언급 한 것 같습니다.
차드 스튜어트

13
명확히하기 위해 : 정의 에서 메소드를 선언@protocol 할 수는 있지만 메소드를 정의 할 수는 없습니다.
Richard

NSException의 범주 메소드를 사용하면 + (instancetype)exceptionForCallingAbstractMethod:(SEL)selector매우 잘 작동합니다.
Patrick Pijnappel

IMHO는 예외를 던지는 것이 다른 개발자에게 더 바람직한 일이라고 생각 doesNotRecognizeSelector합니다.
Chris

추상 클래스는 여기에 주어진 부분 구현 / 흐름 로직이 있거나 템플릿 메소드 패턴 (완전히 추상 클래스)를 사용하여 프로토콜 stackoverflow.com/questions/8146439/...~~V을 . 아래 답변을 참조하십시오.
hadaytullah

267

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

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

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

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

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


5
@ 척, 나는 공감하지 않았지만 NSObject참조는 메서드를 무시하지 않고 메서드를 상속하지 않으려는 경우 이것을 사용하는 것이 좋습니다. 그것들은 아마도 같은 것이지만, 아마도 :)
Dan Rosenstark

이 인스턴스에서 프로토콜을 사용하고 싶지 않은 이유는 무엇입니까? 나에게 이것은 메서드 스텁 만 알고 있지만 전체 추상 클래스는 아는 것이 좋습니다. 이에 대한 사용 사례는 제한적인 것 같습니다.
lewiguez

나는 그것을 공감하지 않았지만, init 메소드에서 예외를 제기해야한다는 제안이 그 이유 일 가능성이 큽니다. 서브 클래스에서 가장 일반적인 형식은 self = [super init]를 호출하여 자체 init 메소드를 시작합니다. 이는 명백하게 예외를 던집니다. 이 형식은 대부분의 메소드에서 잘 작동하지만 하위 클래스가 수퍼 구현이라고 부를 수있는 곳에서는 절대로하지 않습니다.
Xono

5
Objective-C에서 추상 클래스를 절대적으로 만들 수 있으며 매우 일반적입니다. 이를 수행하는 많은 Apple 프레임 워크 클래스가 있습니다. Apple의 추상 클래스는 종종 NSInvalidAbstractInvocation ()을 호출하여 특정 예외 (NSInvalidArgumentException)를 발생시킵니다. 추상 메소드 호출은 프로그래밍 오류이므로 예외가 발생합니다. 추상 팩토리는 일반적으로 클래스 클러스터로 구현됩니다.
quellish

2
@quellish : 당신이 말했듯이 : 추상 메소드 호출은 프로그래밍 오류입니다. 런타임 오류보고 (NSException)에 의존하지 않고 그대로 처리해야합니다. 이것은 Obj-C에서 abstract가 "이 유형의 객체를 인스턴스화 할 수 없음"을 의미하는 다른 언어에서 온 개발자에게 가장 큰 문제라고 생각합니다. "이 클래스를 인스턴스화 할 때 런타임에 문제가 발생합니다"를 의미합니다.
Cross_

60

위의 @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
물론 @TomA, 매크로 할 수있는 다른 코드에 대한 아이디어를 제공하기를 바랍니다. 가장 많이 사용되는 매크로는 싱글 톤에 대한 간단한 참조입니다. 코드에 따르면 universe.thing으로 확장됩니다 [Universe universe].thing. 수천 글자의 코드를 절약하는 큰 재미 ...
Dan Rosenstark

큰. 그래도 조금 변경되었습니다 #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm

1
@Yar : 그렇게 생각하지 않습니다. 우리 __PRETTY_FUNCTION__는 여기에서 제안 된대로 DLog (...) 매크로를 통해 모든 곳에서 사용 합니다 : stackoverflow.com/a/969291/38557 .
noamtm

1
자세한 내용은 -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
GBK

1
유 예를 들어,이 클래스를 상속합니다 10 회를 한 후 기본 클래스를 추가하고 클래스 중 하나에서이를 구현하는 잊어 버린 경우처럼 상속 아니다 u는 기본 클래스의 이름으로 메시지가 표시됩니다 Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'에서 케이스 내가 유도 얻을 제안 HomeViewController - method not implementedHomeViewController이 자료로부터 상속 된 경우 -이 더 많은 정보를 제공 할 것입니다
gbk

42

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

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

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

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


2
+1 이것은 실제로 Java의 추상 클래스에 가장 가까운 솔루션입니다. 나는이 접근법을 직접 사용했으며 훌륭하게 작동합니다. 기본 클래스와 동일하게 프로토콜 이름을 지정할 수도 있습니다 (Apple에서와 마찬가지로 NSObject). 그런 다음 프로토콜과 기본 클래스 선언을 동일한 헤더 파일에 넣으면 추상 클래스와 거의 구별 할 수 없습니다.
codingFriend1 2009 년

아, 그러나 내 추상 클래스는 프로토콜의 일부를 구현하고 나머지는 서브 클래스로 구현됩니다.
Roger CS Wernersson

1
자식 클래스 만 프로토콜을 구현하고 수퍼 클래스 메소드를 모두 비워 두지 않고 그대로 둘 수 있습니다. 그런 다음 Superclass <MyProtocol> 유형의 속성이 있습니다. 또한 유연성을 높이기 위해 프로토콜의 메소드 앞에 @optional을 붙일 수 있습니다.
dotToString

Xcode 5.0.2에서는 작동하지 않는 것 같습니다. "abstract"클래스에 대한 경고 만 생성합니다. "abstract"클래스를 확장하지 않으면 적절한 컴파일러 경고가 생성되지만 메서드를 상속 할 수는 없습니다.
Topher Fangio

이 솔루션을 선호하지만 실제로는 마음에 들지 않으며 프로젝트의 코드 구조가 아닙니다. // 이것을 서브 클래스의 기본 유형으로 사용해야합니다. typedef BaseClass <BaseClassProtocol> BASECLASS; 그것은 단지 일주일 규칙이며, 나는 그것을 좋아하지 않습니다.
Itachi

35

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

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

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

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

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

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

1
나는 -doesNotRecognizeSelector를 사용하는 것에 대해 생각하지 않았다. 나는 어떤 방식 으로이 접근법을 좋아한다. 컴파일러가 이런 방식으로 또는 예외를 발생시켜 생성 된 "추상적 인"메소드에 대한 경고를 발행하는 방법을 아는 사람이 있습니까? 멋진 것입니다 ...
퀸 테일러

26
doesNotRecognizeSelector 접근 방식은 Apple이 제안한 self = [super init] 패턴을 방지합니다.
dlinsin 2009

1
@ david : 요점은 가능한 한 빨리 예외를 제기하는 것입니다. 이상적으로는 컴파일 타임에 있어야하지만 그렇게 할 수있는 방법이 없기 때문에 런타임 예외로 해결되었습니다. 이것은 어설 션 실패와 유사하며, 처음에는 프로덕션 코드에서 발생해서는 안됩니다. 실제로,이 코드가 절대로 실행되지 않아야한다는 것이 확실하기 때문에 assert (false)가 실제로 더 나을 수 있습니다. 사용자는 문제를 해결하기 위해 아무것도 할 수 없으며 개발자가 문제를 해결해야합니다. 따라서 예외 또는 어설 션 실패를 발생시키는 것은 좋은 생각처럼 들립니다.
Senseful

2
서브 클래스가 [super init]에 대한 합법적 인 호출을 할 수 있기 때문에 이것이 가능한 솔루션이라고 생각하지 않습니다.
Raffi Khatchadourian

@ dlinsin : 추상 클래스가 init를 통해 초기화되지 않을 때 정확히 원하는 것입니다. 서브 클래스는 아마도 어떤 super 메소드를 호출해야하는지 알고 있지만 "new"또는 "alloc / init"를 통한 부주의 한 호출을 중지합니다.
gnasher729

21

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

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

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

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


나에게 이것은 최소한 "인터페이스"를 의미 할 때 (C ++에서와 같이) 추상 클래스를 사용하는 경우에 적합한 방법 인 것 같습니다. 이 접근법에 숨겨진 단점이 있습니까?
febeling

1
@febeling, 최소한 Java에서 추상 클래스는 단순한 인터페이스가 아닙니다. 또한 일부 (또는 대부분) 동작을 정의합니다. 그러나이 방법은 어떤 경우에는 좋을 수 있습니다.
Dan Rosenstark

서브 클래스가 모두 공유하는 특정 기능 (복제 제거)을 구현하려면 기본 클래스가 필요하지만 기본 클래스가 처리하지 않아야하는 다른 메소드 (추상 부분)에 대한 프로토콜도 필요합니다. 따라서 하위 클래스가 제대로 구현되도록하기가 까다로울 수있는 곳을 모두 사용해야합니다.
LightningStryk

19

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

그러나 내가 가장 좋아하는 방법은 언급되어 있지 않으며 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. 어설 션 매크로 중 하나를 기반으로하는 접근 방식과 달리 다른 컴파일러 경고 나 오류를 생성하지 않으면 릴리스 빌드에서 제거 할 수 없습니다.

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

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

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


이것은 내가 가장 좋아하는 새로운 솔루션입니다- __builtin_unreachable();보석이 완벽하게 작동합니다. 매크로는 자체 문서화 기능을 제공하며 객체에서 누락 된 메서드를 호출하면 동작이 일치합니다.
Alex MDC

12

사용 @property하고 @dynamic작동 할 수도 있습니다. 동적 속성을 선언하고 일치하는 메서드 구현을 제공하지 않으면 모든 것이 여전히 경고없이 컴파일 unrecognized selector되며 런타임에 액세스하려고하면 런타임에 오류가 발생합니다. 이것은 본질적으로을 호출하는 것과 동일 [self doesNotRecognizeSelector:_cmd]하지만 타이핑이 훨씬 적습니다.


7

Xcode (clang 등을 사용 __attribute__((unavailable(...)))하여)에서 추상 클래스에 태그를 지정하여 사용하려고하면 오류 / 경고가 발생하는 것을 좋아합니다.

이 방법을 실수로 사용하지 못하도록 보호합니다.

기본 클래스 @interface태그에서 "abstract"메소드 :

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

이 한 단계 더 나아가 매크로를 만듭니다.

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

이를 통해 다음을 수행 할 수 있습니다.

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

내가 말했듯이, 이것은 실제 컴파일러 보호는 아니지만 추상 메소드를 지원하지 않는 언어를 사용하는 것만 큼 좋습니다.


1
나는 그것을 얻을 수 없었다. 내 기본 클래스에 제안을 적용 -init의 방법과 지금 엑스 코드 나에게 상속 된 클래스의 인스턴스와 컴파일시 에러가 발생 만들 수 없습니다 사용할 수 없습니다를 ... . 더 설명해 주시겠습니까?
anonim

-init서브 클래스에 메소드 가 있는지 확인하십시오 .
Richard Stelling

2
NS_UNAVAILABLE과 동일하므로 해당 속성으로 표시된 메소드를 호출하려고 할 때마다 오류가 발생합니다. 추상 클래스에서 어떻게 사용할 수 있는지 알지 못합니다.
Ben Sinclair

7

질문에 대한 답변은 이미 주어진 답변 아래 주석에 흩어져 있습니다. 그래서 나는 여기서 요약하고 단순화하고 있습니다.

옵션 1 : 프로토콜

구현하지 않고 추상 클래스를 만들려면 '프로토콜'을 사용하십시오. 프로토콜을 상속받는 클래스는 프로토콜의 메소드를 구현해야합니다.

@protocol ProtocolName
// list of methods and properties
@end

옵션 2 : 템플릿 방법 패턴

"템플릿 메소드 패턴"과 같은 부분 구현으로 추상 클래스를 작성하려면 이것이 솔루션입니다. Objective-C-템플릿 메소드 패턴?


6

다른 대안

Abstract 클래스와 Assert 또는 Exception에서 클래스를 확인하십시오.

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

이것은 무시할 필요성을 제거합니다 init


그냥 nil을 반환하는 것이 효율적입니까? 아니면 예외 / 다른 오류가 발생합니까?
user2277872

글쎄 이것은 클래스를 직접 사용하는 프로그래머를 잡는 것입니다. 어설 션을 잘 사용한다고 생각합니다.
bigkm

5

(관련 제안 더)

프로그래머에게 "자식에서 전화하지 마십시오"를 알리고 완전히 재정의하는 방법을 원했습니다 (내 경우에는 확장되지 않은 경우 부모 대신 기본 기능을 여전히 제공합니다).

typedef void override_void;
typedef id override_id;

@implementation myBaseClass

// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;

// some internally required default behavior
- (void) doesSomethingImportant;

@end

장점은 프로그래머가 선언에서 "재정의"를보고 호출하지 않아야한다는 것을 알 수 있다는 것 [super ..]입니다.

당연히, 이것에 대한 개별 반환 유형을 정의해야하지만, 충분한 시각적 힌트 ​​역할을하며 하위 클래스 정의에서 "override_"부분을 쉽게 사용할 수 없습니다.

물론 확장이 선택적 일 때 클래스는 여전히 기본 구현을 가질 수 있습니다. 그러나 다른 답변에서 알 수 있듯이 추상 (가상) 클래스와 같이 적절한 경우 런타임 예외를 구현하십시오.

주석과 문서를 파헤 치거나 가정하지 않고 수퍼의 구현을 사전 / 사후 호출하는 것이 가장 좋은시기에 대한 힌트조차도 이와 같은 컴파일러 힌트를 내장하는 것이 좋습니다.

힌트의 예


4

다른 언어로 추상 인스턴스화 위반을 포착하는 컴파일러에 익숙하다면 Objective-C 동작이 실망입니다.

늦은 바인딩 언어로서 Objective-C는 클래스가 실제로 추상적인지 아닌지에 대한 정적 결정을 내릴 수 없다는 것이 분명합니다 (런타임에 함수를 추가 할 수 있습니다 ...). 일반적인 사용 사례의 경우 이는 단점으로 보입니다. 컴파일러에서 런타임에 오류를 발생시키는 대신 추상 클래스의 인스턴스화를 방지하는 것이 좋습니다.

다음은 이니셜 라이저를 숨기는 몇 가지 기술을 사용하여 이러한 유형의 정적 검사를 수행하는 데 사용하는 패턴입니다.

//
//  Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));

@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end

@interface Base : NSObject {
    @protected
    __weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}

- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end

//
//  Base.m
#import "Base.h"

// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end

@implementation Base
- (instancetype)initFromDerived {
    // It is unlikely that this becomes incorrect, but assert
    // just in case.
    NSAssert(![self isMemberOfClass:[Base class]],
             @"To be called only from derived classes!");
    self = [super init];
    return self;
}

- (void) doStuffUsingDependentFunction {
    [_protocolHelper dependentFunction]; // Use it
}
@end

//
//  Derived.h
#import "Base.h"

@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end

//
//  Derived.m
#import "Derived.h"

// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end

// Privately inherit protocol
@interface Derived () <MyProtocol>
@end

@implementation Derived
-(instancetype) initDerived {
    self= [super initFromDerived];
    if (self) {
        self->_protocolHelper= self;
    }
    return self;
}

// Implement the missing function
-(void)dependentFunction {
}
@end

3

아마도 이런 종류의 상황은 개발 시점에만 발생해야하므로 다음과 같이 작동 할 수 있습니다.

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

3

@Yar에서 제안한 방법을 사용할 수 있습니다 (일부 수정).

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()

여기에 다음과 같은 메시지가 나타납니다.

<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'

또는 주장 :

NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");

이 경우 다음을 얻을 수 있습니다.

<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53

또한 프로토콜 및 기타 솔루션을 사용할 수 있지만 가장 간단한 솔루션 중 하나입니다.


2

코코아 는 abstract라는 것을 제공하지 않습니다. 우리는 런타임시에만 검사되는 클래스 추상을 만들 수 있으며 컴파일 타임에는 검사되지 않습니다.


1

나는 보통 추상화하려는 클래스에서 init 메소드를 비활성화합니다.

- (instancetype)__unavailable init; // This is an abstract class.

해당 클래스에서 init를 호출 할 때마다 컴파일 타임에 오류가 발생합니다. 그런 다음 다른 모든 것에 클래스 메소드를 사용합니다.

Objective-C에는 추상 클래스를 선언하기위한 기본 제공 방법이 없습니다.


그러나 해당 추상 클래스의 파생 클래스에서 [super init]를 호출하면 오류가 발생합니다. 그것을 해결하는 방법?
Sahil Doshi

@SahilDoshi이 방법을 사용하는 것이 단점이라고 생각합니다. 클래스를 인스턴스화하지 않으려는 경우 사용하고 클래스에서 상속되는 것이 없습니다.
mahoukov

예, 이해했습니다. 그러나 귀하의 솔루션은 누군가가 init를 호출하지 않기를 원하는 싱글 톤 클래스와 같은 것을 만드는 데 도움이 될 것입니다. 추상 클래스의 경우 내 지식에 따라 일부 클래스는 상속받을 것입니다. 그렇지 않으면 추상 클래스의 사용은 무엇입니까?
Sahil Doshi

0

@dotToString의 주석을 적용하여 @redfood가 제안한 것을 약간 변경하면 실제로 Instagram의 IGListKit에서 채택한 솔루션이 있습니다.

  1. 기본 (추상) 클래스에서 정의 할 수없는 모든 메소드에 대한 프로토콜을 작성하십시오. 즉, 하위에서 특정 구현이 필요합니다.
  2. 이 프로토콜을 구현 하지 않는 기본 (추상) 클래스를 만듭니다 . 이 클래스에 공통 구현이 필요한 다른 메소드를 추가 할 수 있습니다.
  3. 프로젝트의 어느 곳에서나 AbstractClass어떤 방법 으로 자식을 입력하거나 출력 해야하는 경우 AbstractClass<Protocol>대신 입력하십시오 .

AbstractClass이 구현하지 않기 때문에 인스턴스 Protocol를 갖는 유일한 방법 AbstractClass<Protocol>은 서브 클래 싱입니다. 으로 AbstractClass혼자이 프로젝트에 어디서나 사용할 수 없습니다, 그것은 추상적된다.

물론 이것은 조언을받지 않은 개발자가 단순히를 참조하는 새로운 메소드를 추가하는 것을 막지는 못합니다. AbstractClass결국 더 이상은 아닌 추상 클래스의 인스턴스를 허용하게됩니다.

실제 예 : IGListKit 에는 IGListSectionController프로토콜을 구현하지 않는 기본 클래스 가 IGListSectionType있지만 해당 클래스의 인스턴스가 필요한 모든 메소드는 실제로 type을 요청합니다 IGListSectionController<IGListSectionType>. 따라서 IGListSectionController프레임 워크에 유용한 어떤 유형의 객체도 사용할 수 없습니다 .


0

실제로 Objective-C에는 추상 클래스가 없지만 프로토콜 을 사용 하여 동일한 효과를 얻을 수 있습니다 . 샘플은 다음과 같습니다.

CustomProtocol.h

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end

0

추상 클래스를 만드는 간단한 예

// Declare a protocol
@protocol AbcProtocol <NSObject>

-(void)fnOne;
-(void)fnTwo;

@optional

-(void)fnThree;

@end

// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>

@end

@implementation AbstractAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

-(void)fnOne{
// Code
}

-(void)fnTwo{
// Code
}

@end

// Implementation class
@interface ImpAbc : AbstractAbc

@end

@implementation ImpAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

// You may override it    
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}

-(void)fnThree{
// Code
}

@end

-3

대리인을 만들 수 없습니까?

델리게이트는 어떤 함수를 정의해야하는지 말한다는 점에서 추상 기본 클래스와 비슷하지만 실제로 정의하지는 않습니다.

그런 다음 델리게이트 (예 : 추상 클래스)를 구현할 때마다 컴파일러에서 동작을 정의해야하는 선택적 및 필수 함수에 대해 경고합니다.

이것은 나에게 추상 기본 클래스처럼 들립니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.