私はもともと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;
}
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
を使用する方が正しいと思います(NSObject
のドキュメントを参照)。
私が思いついた解決策は次のとおりです。
このようにして、コンパイラはあなたの子クラスでは実装されていないプロトコル内のどのメソッドに対しても警告を出します。
Javaほど簡潔ではありませんが、必要なコンパイラ警告が表示されます。
現時点では、Objective-CにはJavaのような抽象コンパイラ構成はありません。
つまり、抽象クラスを他の通常のクラスと同じように定義し、空の、またはselectorがサポートされていないことを報告する抽象メソッドのメソッドスタブを実装するだけです。例えば...
- (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はOperationプロトコルを実装する任意のオブジェクトです。
抽象基本クラスが単にメソッドを定義する以上のことをする必要がある場合は、通常のObjective-Cクラスを作成して、インスタンス化されないようにすることができます。 - (id)init関数をオーバーライドしてnilまたはassert(false)を返すようにするだけです。これはあまりきれいな解決策ではありませんが、Objective-Cは完全に動的であるため、抽象基底クラスに直接相当するものは実際にはありません。
このスレッドは古いもので、共有したいもののほとんどはすでにここにあります。
しかし、私の好きな方法は言及されておらず、私の知る限り、現在のClangにはネイティブサポートがありません。
何よりもまず(他の人が既に指摘しているように)抽象クラスはObjective-Cでは非常に珍しいものです。通常、代わりに合成(場合によっては委任による)を使用します。これはおそらく、CoreDataの導入に伴ってObjC 2.0でIIRCが追加された@dynamic
プロパティを除いて、そのような機能が言語/コンパイラにまだ存在しない理由です。
しかし、(あなたの状況を慎重に評価した後!)あなたは委任(または一般的な構成)があなたの問題を解決するのにあまり適していないという結論に達したので、ここにIやる:
[self doesNotRecognizeSelector:_cmd];
…にする__builtin_unreachable();
が続き、非voidメソッドに対して表示される警告を黙らせて、「コントロールが戻りなしで非void関数の終わりに達した」ことを伝えます。__attribute__((__noreturn__))
を使用して-[NSObject doesNotRecognizeSelector:]
に注釈を付けますそのメソッドの元の実装を置き換えずに、プロジェクトの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
ご覧のとおり、マクロは抽象メソッドの完全な実装を提供し、必要な定型文の量を最小限に抑えています。
さらに良いオプションは、機能リクエストを介して、この場合のコンパイラー属性を提供する ロビーtheClangチーム です。 (これにより、NSIncrementalStoreをサブクラス化するシナリオのコンパイル時診断も有効になるためです。)
__builtin_unreachable()
は人々を驚かせるかもしれませんが、理解するのも簡単です。)その最後のポイントにはいくつかの説明が必要です、私は推測します:
一部の(ほとんどの?)人はリリースビルドでアサーションを取り除きます。 (私はその習慣に同意しませんが、それは別の話です…)必要なメソッドの実装に失敗する—しかし—はbad、terrible、wrong、および基本的に宇宙の終わりプログラムの場合。プログラムは未定義であり、未定義の動作がこれまでで最悪であるため、この点で正しく動作できません。したがって、新しい診断を生成せずにそれらの診断を除去できることは、まったく受け入れられないでしょう。
このようなプログラマーのエラーに対して適切なコンパイル時診断を取得できず、これらのエラーを実行時に検出する必要があるのは十分に悪いことですが、リリースビルドでそれを使用できない場合は、なぜ抽象クラスを最初の場所?
@property
と@dynamic
を使ってもうまくいくでしょう。動的プロパティを宣言しても、対応するメソッド実装を指定しないと、警告なしですべてがコンパイルされ、アクセスしようとすると実行時にunrecognized selector
エラーが発生します。これは基本的に[self doesNotRecognizeSelector:_cmd]
を呼び出すのと同じことですが、入力ははるかに少なくなります。
質問に対する答えは、すでに与えられた答えの下のコメントに散らばっています。それで、ここで要約して単純化しているだけです。
実装しないで抽象クラスを作成したい場合は 'Protocols'を使用してください。プロトコルを継承するクラスは、プロトコル内のメソッドを実装する必要があります。
@protocol ProtocolName
// list of methods and properties
@end
あなたが "テンプレートメソッドパターン"のような部分的な実装で抽象クラスを作成したいなら、これは解決策です。 Objective-C - テンプレートメソッドのパターン?
Xcode(clangなどを使用)では、抽象クラスをタグ付けするために__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");
私が言ったように、これは本当のコンパイラ保護ではありませんが、抽象メソッドをサポートしていない言語に入るのと同じくらい良いです。
他の選択肢
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
また、プロトコルや他の解決策を使用することができます - しかしこれは最も単純なものの一つです。
おそらくこの種の状況は開発時にのみ起こるはずなので、これでうまくいく可能性があります。
- (id)myMethodWithVar:(id)var {
NSAssert(NO, @"You most override myMethodWithVar:");
return nil;
}
Cocoa abstractと呼ばれるものは何も提供されていません。実行時にのみチェックされるクラス抽象を作成することができ、コンパイル時にはチェックされません。
抽象化したいクラスのinitメソッドを無効にするだけです。
- (instancetype)__unavailable init; // This is an abstract class.
そのクラスでinitを呼び出すと、コンパイル時にエラーが発生します。それから私は他のすべてのためにクラスメソッドを使います。
Objective-Cには、抽象クラスを宣言するための組み込みの方法はありません。
抽象クラスを作成する簡単な例
// 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
実際、Objective-Cには抽象クラスがありませんが、同じ効果を得るためにProtocolsを使用できます。これがサンプルです。
#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
@dotToStringのコメントを適用することで@redfoodが提案したことを少し変更すると、実際にはInstagramの IGListKit で採用されたソリューションが得られます。
AbstractClass
の子を何らかのメソッドで入出力する必要がある場合は、代わりにAbstractClass<Protocol>
として入力します。AbstractClass
はProtocol
を実装しないため、AbstractClass<Protocol>
インスタンスを持つ唯一の方法はサブクラス化することです。 AbstractClass
だけをプロジェクトのどこでも使用できないため、抽象になります。
もちろん、これは、賢明ではない開発者が単にAbstractClass
を参照する新しいメソッドを追加することを妨げるものではありません。
実際の例: IGListKit には、プロトコルIGListSectionController
を実装しない基本クラスIGListSectionType
がありますが、そのクラスのインスタンスを必要とするすべてのメソッドは、実際に型を要求しますIGListSectionController<IGListSectionType>
。したがって、IGListSectionController
型のオブジェクトをフレームワークで有用なものに使用する方法はありません。