programing

Objective-C에서 추상 클래스 만들기

jooyons 2023. 4. 10. 21:37
반응형

Objective-C에서 추상 클래스 만들기

저는 원래 Objective-C에서 일하는 자바 프로그래머입니다.추상 클래스를 만들고 싶은데 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];

따라서 메서드에서 반환문을 추가할 필요가 없습니다.

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

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

메서드/실렉터가 doesNotRecognize를 호출하도록 함으로써 추상 클래스를 조롱할 수 있습니다.Selector: 따라서 클래스를 사용할 수 없도록 예외를 발생시킵니다.

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

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

init을 위해서도 이 작업을 할 수 있습니다.

위의 @Barry Wark의 답변(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)NSObjectdocs를 해 주세요.

제가 생각해낸 솔루션은 다음과 같습니다.

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

이렇게 하면 컴파일러는 자식 클래스에서 구현되지 않은 프로토콜의 메서드에 대해 경고를 보냅니다.

Java에서처럼 간단하지는 않지만 원하는 컴파일러 경고를 받습니다.

Omni Group 메일링 리스트:

Objective-C는 현재 Java와 같은 추상 컴파일러 구조를 가지고 있지 않습니다.

따라서 추상 클래스를 다른 일반 클래스로 정의하고 비어 있거나 실렉터에 대한 비지원 보고서를 제출한 추상 메서드에 대해 메서드 stub을 구현하기만 하면 됩니다.예를 들면...

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

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

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

추상 기본 클래스를 만드는 대신 프로토콜을 사용하는 것이 좋습니다(Java 인터페이스와 유사).이렇게 하면 메서드 집합을 정의한 다음 프로토콜을 준수하는 모든 개체를 수락하고 메서드를 구현할 수 있습니다.예를 들어 Operation Protocol을 정의하고 다음과 같은 함수를 사용할 수 있습니다.

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

여기서 op은 Operation 프로토콜을 구현하는 모든 개체일 수 있습니다.

단순히 메서드를 정의하는 것 이상을 수행해야 하는 추상 기본 클래스가 필요한 경우 일반 Objective-C 클래스를 만들어 인스턴스화되지 않도록 할 수 있습니다.- (id)init 함수를 덮어쓰고 nero 또는 assert(false)를 반환합니다.매우 깨끗한 솔루션은 아니지만 Objective-C는 완전히 동적이기 때문에 추상적인 기본 클래스에 직접 해당하는 솔루션은 없습니다.

이 실타래는 좀 오래되었고, 제가 공유하고 싶은 것의 대부분은 이미 여기에 있습니다.

다만, 마음에 드는 방법은 기재되어 있지 않고, AFAIK는 현재의 Clang에서는 네이티브 서포트가 없기 때문에, 여기 있습니다.

첫째, 무엇보다도 (다른 사람들이 이미 지적한 바와 같이) 추상 클래스는 Objective-C에서 매우 드문 것입니다.대신 (때로는 위임을 통해) 구성을 사용합니다.아직 하지 않는 수 .이거는 언어/컴파일러에 @dynamicIIRC(CoreData) ObjC 2.0(ObjC 2.0).

그러나 (자신의 상황을 신중하게 평가한 결과) 위임(또는 구성)이 문제를 해결하는 데 적합하지 않다는 결론에 도달했습니다.그 방법은 다음과 같습니다.

  1. 기본 클래스에서 모든 추상 메서드를 구현합니다.
  2. 실장을 하다 그장 make[self doesNotRecognizeSelector:_cmd];
  3. 의 을 받다__builtin_unreachable();"컨트롤이 반환 없이 비반전 함수의 끝에 도달했습니다"라고 알려줌으로써 비반전 메서드에 대해 발생하는 경고를 중지합니다.
  4. 하거나 3단계에 .-[NSObject doesNotRecognizeSelector:]를 사용합니다.__attribute__((__noreturn__))구현되지 않은 카테고리에서 해당 메서드의 원래 구현을 대체하지 않도록 하고 프로젝트의 PCH에 해당 카테고리의 헤더를 포함합니다.

저는 개인적으로 보일러 플레이트를 최대한 줄일 수 있는 매크로 버전을 선호합니다.

여기 있습니다.

// 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 팀이 기능 요청을 통해 이 케이스의 컴파일러 속성을 제공하도록 로비를 하는 입니다.(이를 통해 NSIncremental Store 등의 서브클래스를 사용하는 시나리오에 대해서도 컴파일 시 진단이 가능해지므로 더욱 좋습니다.)

이 방법을 선택하는 이유

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

마지막 부분은 설명이 필요한 것 같아요.

일부(대부분?)는 릴리즈 빌드에서 어설션을 배제합니다.(그 습관에 동의하지 않지만 그건 다른 이야기입니다.) 그러나 필요한 방법을 구현하지 못하는 나쁘고, 끔찍하고, 잘못된 것이며, 기본적으로 당신의 프로그램에는 우주의 종말이 있습니다.프로그램이 정의되지 않았기 때문에 이 점에서 올바르게 동작할 수 없으며, 정의되지 않은 동작은 사상 최악의 것입니다.따라서 새로운 진단을 생성하지 않고 이러한 진단을 삭제할 수 있는 것은 완전히 받아들일 수 없습니다.

이러한 프로그래머 오류에 대해 적절한 컴파일 시간 진단을 얻을 수 없고 실행 시 검출에 의존해야 하는 것은 매우 유감입니다만, 릴리스 빌드에서 이 문제를 검토할 수 있다면 애초에 추상적인 클래스가 필요한 이유는 무엇입니까?

「」를 사용합니다.@property ★★★★★★★★★★★★★★★★★」@dynamic될 수도 있어요.하는 메서드의이 경고 모든 것이 컴파일됩니다.또, 「」가 됩니다.unrecognized selector액세스하려고 하면 런타임에 오류가 발생합니다.으로 '부르다'라고 부르는 .[self doesNotRecognizeSelector:_cmd] 타이핑은 훨씬

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

이 방법을 잘못 사용하는 것을 방지합니다.

클래스에서는, 「」를 참조해 주세요.@interface"이것들"은 다음과 같습니다.

- (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: 프로토콜

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

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

옵션 2:템플릿 메서드 패턴

"Template Method Pattern"과 같은 부분 구현으로 추상 클래스를 만드는 경우 이 방법이 해결책입니다.목표-C - 템플릿 메서드 패턴?

또 다른 대안

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

(더 많은 관련 제안)

저는 프로그래머에게 "자녀로부터 호출하지 않음"을 알리고 완전히 재정의할 수 있는 방법을 갖고 싶었습니다(내 경우 확장되지 않은 경우에도 부모 대신 기본 기능을 제공합니다).

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_" 부분을 쉽게 사용할 수 없습니다.

물론 확장이 옵션인 경우에도 클래스에 기본 구현이 있을 수 있습니다.그러나 다른 답변과 마찬가지로 추상(가상) 수업과 같이 적절한 경우 런타임 예외를 구현하십시오.

이러한 컴파일러 힌트를 짜넣어 두면 좋을 것입니다.또, 코멘트나 문서를 뒤지는 일 없이, 슈퍼의 실장을 사전/사후 호출하는 것이 가장 좋은 타이밍에 관한 힌트도 짜넣어 두면 좋을 것입니다.추정하다.

힌트의 예

컴파일러가 다른 언어로 추상 인스턴스화 위반을 탐지하는 데 익숙하다면 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

이러한 상황은 개발 시에만 발생할 수 있기 때문에 다음과 같이 동작할 수 있습니다.

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

@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

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

코코아는 추상이라는 것을 제공하지 않는다.실행 시에만 체크되고 컴파일 시에는 체크되지 않는 클래스 추상화를 만들 수 있습니다.

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

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

그러면 해당 클래스에서 init을 호출할 때마다 컴파일 시 오류가 발생합니다.그리고 나서 나는 다른 모든 것에 수업 방법을 사용한다.

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

@dotToString의 코멘트를 적용하여 @redfood가 제안하는 것을 조금 바꾸면, 실제로 Instagram의 IGListKit에서 채택한 솔루션이 있습니다.

  1. 기본(추상) 클래스에서 정의할 수 없는 모든 메서드에 대한 프로토콜을 만듭니다. 즉, 하위 클래스에서 특정 구현이 필요합니다.
  2. 이 프로토콜을 구현하지 않는 기본(추상) 클래스를 만듭니다.공통 구현에 적합한 다른 메서드를 이 클래스에 추가할 수 있습니다.
  3. 프로젝트 내 모든 곳에서 아이가AbstractClass되어야 합니다.하면 됩니다.AbstractClass<Protocol>★★★★★★ 。

★★★★★★★★★★★★★★★★★★AbstractClass does does does does 。Protocol 「 」를 가지는 .AbstractClass<Protocol>subclassing에 의한 입니다.~로AbstractClass프로젝트 내 어디에서도 사용할 수 없기 때문에 추상화됩니다.

물론, 이것은 단순히 다음을 참조하는 새로운 방법을 추가하는 것을 막지는 않습니다.AbstractClass(더 이상) 추상 클래스의 인스턴스가 허용됩니다.

실제 예: IGListKit에는 기본 클래스가 있습니다.IGListSectionController되지 않습니다.IGListSectionType 그 로 하는 합니다.IGListSectionController<IGListSectionType> 오브젝트를 할 수 따라서 타입의 오브젝트를 사용할 방법이 없습니다.IGListSectionController★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

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

Custom Protocol.h

#import <Foundation/Foundation.h>

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

Test Protocol.h

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

@interface TestProtocol : NSObject <CustomProtocol>

@end

Test Protocol.m

#import "TestProtocol.h"

@implementation TestProtocol

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

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

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

// 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

그냥 대표자를 만들면 안 돼요?

어떤 함수를 정의해야 하는지 말하기는 하지만 실제로 정의하지는 않는다는 점에서 대리자는 추상 기본 클래스와 같습니다.

그런 다음 위임자(즉 추상 클래스)를 구현할 때마다 동작을 정의해야 하는 옵션 및 필수 함수에 대해 컴파일러로부터 경고를 받습니다.

이건 추상적인 기본 수업처럼 들리는데요.

언급URL : https://stackoverflow.com/questions/1034373/creating-an-abstract-class-in-objective-c

반응형