저는 원래 Objective-C와 함께 일하는 Java 프로그래머입니다. 추상 클래스를 만들고 싶지만 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 프로토콜을 사용하는 것이 더 적합한 옵션입니다.
@protocol
할 수는 있지만 메소드를 정의 할 수는 없습니다.
+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
매우 잘 작동합니다.
doesNotRecognizeSelector
합니다.
아니요, Objective-C에서 추상 클래스를 만들 수있는 방법이 없습니다.
메소드 / 선택자가 doesNotRecognizeSelector를 호출하여 추상 클래스를 조롱 할 수 있으므로 클래스를 사용할 수 없게하는 예외가 발생합니다.
예를 들면 다음과 같습니다.
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
초기화를 위해이 작업을 수행 할 수도 있습니다.
NSObject
참조는 메서드를 무시하지 않고 메서드를 상속하지 않으려는 경우 이것을 사용하는 것이 좋습니다. 그것들은 아마도 같은 것이지만, 아마도 :)
위의 @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
문서 참조 ).
universe.thing
으로 확장됩니다 [Universe universe].thing
. 수천 글자의 코드를 절약하는 큰 재미 ...
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.
__PRETTY_FUNCTION__
는 여기에서 제안 된대로 DLog (...) 매크로를 통해 모든 곳에서 사용 합니다 : stackoverflow.com/a/969291/38557 .
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'
에서 케이스 내가 유도 얻을 제안 HomeViewController - method not implemented
HomeViewController이 자료로부터 상속 된 경우 -이 더 많은 정보를 제공 할 것입니다
내가 생각해 낸 해결책은 다음과 같습니다.
이런 식으로 컴파일러는 자식 클래스에 의해 구현되지 않은 프로토콜의 모든 메소드에 대해 경고합니다.
Java처럼 간결하지는 않지만 원하는 컴파일러 경고가 표시됩니다.
NSObject
). 그런 다음 프로토콜과 기본 클래스 선언을 동일한 헤더 파일에 넣으면 추상 클래스와 거의 구별 할 수 없습니다.
로부터 옴니 그룹 메일 링리스트 :
현재 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에 추가되었습니다.
그러나 위임 (또는 일반적으로 조성물) 당신의 문제를 해결하는 데 적합하지 않습니다 있다는 결론에 도달했다 (! 상황의주의 깊은 평가 후) 여기 방법은 주어진 나는 그것을 할 :
[self doesNotRecognizeSelector:_cmd];
…__builtin_unreachable();
공백하지 않은 방법에 대해 경고 메시지를 끄면“반환없이 비 공백 기능의 끝에 도달했다는 제어”를 알려줍니다.-[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와 같은 서브 클래스 시나리오에서 컴파일 타임 진단을 가능하게하기 때문에 더 좋습니다.)
__builtin_unreachable()
사람들을 놀라게 할 수도 있지만 이해하기도 쉽습니다.)마지막 요점은 약간의 설명이 필요합니다.
릴리스 빌드에서 일부 (가장?) 사람들이 어설 션을 제거합니다. (나는 그 습관에 동의하지 않지만, 그것은 또 다른 이야기이다 ...) 그러나 필요한 방법을 구현하지 못하는 것은 – 나쁜 , 끔찍한 , 잘못된 , 그리고 기본적으로 당신의 프로그램을위한 우주의 끝이다 . 프로그램이 정의되지 않았기 때문에 프로그램이 이와 관련하여 올바르게 작동 할 수 없으며 정의되지 않은 동작이 최악입니다. 따라서 새로운 진단을 생성하지 않고 해당 진단을 제거 할 수 있으면 완전히 수용 할 수 없습니다.
그러한 프로그래머 오류에 대해 적절한 컴파일 타임 진단을 얻을 수 없으며 런타임 오류 발견에 의존해야하지만 릴리스 빌드에서 오류를 해결할 수 있다면 왜 추상 클래스를 사용해보십시오. 처음?
__builtin_unreachable();
보석이 완벽하게 작동합니다. 매크로는 자체 문서화 기능을 제공하며 객체에서 누락 된 메서드를 호출하면 동작이 일치합니다.
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");
내가 말했듯이, 이것은 실제 컴파일러 보호는 아니지만 추상 메소드를 지원하지 않는 언어를 사용하는 것만 큼 좋습니다.
-init
서브 클래스에 메소드 가 있는지 확인하십시오 .
질문에 대한 답변은 이미 주어진 답변 아래 주석에 흩어져 있습니다. 그래서 나는 여기서 요약하고 단순화하고 있습니다.
구현하지 않고 추상 클래스를 만들려면 '프로토콜'을 사용하십시오. 프로토콜을 상속받는 클래스는 프로토콜의 메소드를 구현해야합니다.
@protocol ProtocolName
// list of methods and properties
@end
"템플릿 메소드 패턴"과 같은 부분 구현으로 추상 클래스를 작성하려면 이것이 솔루션입니다. Objective-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
@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에서 채택한 솔루션이 있습니다.
AbstractClass
어떤 방법 으로 자식을 입력하거나 출력 해야하는 경우 AbstractClass<Protocol>
대신 입력하십시오 .AbstractClass
이 구현하지 않기 때문에 인스턴스 Protocol
를 갖는 유일한 방법 AbstractClass<Protocol>
은 서브 클래 싱입니다. 으로 AbstractClass
혼자이 프로젝트에 어디서나 사용할 수 없습니다, 그것은 추상적된다.
물론 이것은 조언을받지 않은 개발자가 단순히를 참조하는 새로운 메소드를 추가하는 것을 막지는 못합니다. AbstractClass
결국 더 이상은 아닌 추상 클래스의 인스턴스를 허용하게됩니다.
실제 예 : IGListKit 에는 IGListSectionController
프로토콜을 구현하지 않는 기본 클래스 가 IGListSectionType
있지만 해당 클래스의 인스턴스가 필요한 모든 메소드는 실제로 type을 요청합니다 IGListSectionController<IGListSectionType>
. 따라서 IGListSectionController
프레임 워크에 유용한 어떤 유형의 객체도 사용할 수 없습니다 .
실제로 Objective-C에는 추상 클래스가 없지만 프로토콜 을 사용 하여 동일한 효과를 얻을 수 있습니다 . 샘플은 다음과 같습니다.
#import <Foundation/Foundation.h>
@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end
#import <Foundation/Foundation.h>
#import "CustomProtocol.h"
@interface TestProtocol : NSObject <CustomProtocol>
@end
#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
대리인을 만들 수 없습니까?
델리게이트는 어떤 함수를 정의해야하는지 말한다는 점에서 추상 기본 클래스와 비슷하지만 실제로 정의하지는 않습니다.
그런 다음 델리게이트 (예 : 추상 클래스)를 구현할 때마다 컴파일러에서 동작을 정의해야하는 선택적 및 필수 함수에 대해 경고합니다.
이것은 나에게 추상 기본 클래스처럼 들립니다.