510

私はもともと Java プログラマーで、現在は Objective-C を扱っています。抽象クラスを作成したいのですが、Objective-C ではそれができないようです。これは可能ですか?

そうでない場合、Objective-C でどれだけ抽象クラスに近づけることができますか?

4

21 に答える 21

635

通常、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 プロトコルを使用する方が適切なオプションです。

于 2009-06-23T18:56:24.323 に答える
266

いいえ、Objective-C で抽象クラスを作成する方法はありません。

メソッド/セレクターが doesNotRecognizeSelector: を呼び出すようにすることで、抽象クラスをモックできます。したがって、クラスを使用できなくする例外を発生させます。

例えば:

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

これは、init でも実行できます。

于 2009-06-23T18:47:03.190 に答える
60

上記の@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( ではなく)を使用する方が正しいと思います(ドキュメントを参照)。NSInternalInconsistencyExceptiondoesNotRecognizeSelectorNSObject

于 2011-06-23T02:39:53.650 に答える
41

私が思いついた解決策は次のとおりです。

  1. 「抽象」クラスで必要なものすべてのプロトコルを作成します
  2. プロトコルを実装する基本クラスを作成します (または抽象クラスと呼ぶこともできます)。「抽象」が必要なすべてのメソッドについて、それらを .m ファイルに実装しますが、.h ファイルには実装しません。
  3. 子クラスに基本クラスから継承させ、プロトコルを実装します。

このようにして、コンパイラは、子クラスによって実装されていないプロトコルのメソッドに対して警告を出します。

Java ほど簡潔ではありませんが、必要なコンパイラ警告が表示されます。

于 2012-06-21T19:03:40.283 に答える
36

Omni Group のメーリング リストから:

現時点では、Objective-C には Java のような抽象的なコンパイラ構造はありません。

したがって、抽象クラスを他の通常のクラスとして定義し、空であるか、セレクターの非サポートを報告する抽象メソッドのメソッド スタブを実装するだけです。例えば...

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

また、デフォルトの初期化子による抽象クラスの初期化を防ぐために、次のことを行います。

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}
于 2009-06-23T18:45:39.587 に答える
21

抽象基本クラスを作成しようとする代わりに、プロトコルの使用を検討してください (Java インターフェースに似ています)。これにより、一連のメソッドを定義し、プロトコルに準拠するすべてのオブジェクトを受け入れてメソッドを実装できます。たとえば、Operation プロトコルを定義して、次のような関数を作成できます。

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

op は、Operation プロトコルを実装する任意のオブジェクトにすることができます。

単にメソッドを定義するだけでなく、抽象基本クラスが必要な場合は、通常の Objective-C クラスを作成して、インスタンス化されないようにすることができます。- (id)init 関数をオーバーライドして、nil または assert(false) を返すようにするだけです。これはあまりクリーンなソリューションではありませんが、Objective-C は完全に動的であるため、抽象基底クラスに直接相当するものは実際にはありません。

于 2009-06-23T18:57:32.513 に答える
19

このスレッドは古いもので、共有したい内容のほとんどは既にここにあります。

ただし、私のお気に入りの方法については言及されていません。また、現在の Clang にはネイティブ サポートがないことがわかります。

何よりもまず (他の人がすでに指摘しているように) 抽象クラスは Objective-C では非常に珍しいものです — 通常、代わりに合成 (時には委任による) を使用します。@dynamicこれが、CoreData の導入に伴い ObjC 2.0 で追加されたプロパティを除いて、そのような機能が言語/コンパイラにまだ存在しない理由です。

しかし、(あなたの状況を注意深く評価した後!) 委譲 (または一般的な構成) は問題の解決にはあまり適していないという結論に達したことを考えると、次のようします。

  1. 基本クラスにすべての抽象メソッドを実装します。
  2. その実装を行う[self doesNotRecognizeSelector:_cmd];...</li>
  3. …続いて__builtin_unreachable();、非 void メソッドに対して表示される警告を黙らせ、「制御が戻りなしで非 void 関数の終わりに達した」ことを伝えます。
  4. マクロで手順 2. と 3. を組み合わせるか、そのメソッドの元の実装を置き換えないように実装のないカテゴリで-[NSObject doesNotRecognizeSelector:]usingに注釈を付け、プロジェクトの 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. アサーション マクロの 1 つに基づくアプローチとは異なり、他のコンパイラ警告またはエラーを生成せずにリリース ビルドで削除することはできません。

最後の点については、説明が必要だと思います。

一部の (ほとんどの?) 人々は、リリース ビルドでアサーションを取り除きます。(私はその習慣に同意しませんが、それはまた別の話です…) 必要なメソッドを実装できないことは — しかし —悪いひどい間違っている、そして基本的にあなたのプログラムの宇宙の終わりです。あなたのプログラムは未定義であるため、この点で正しく動作しません。未定義の動作はこれまでで最悪です。したがって、新しい診断を生成せずにこれらの診断を取り除くことは、まったく受け入れられません。

このようなプログラマ エラーの適切なコンパイル時診断を取得できず、これらを実行時に発見する手段に頼らなければならないのは十分に悪いことですが、リリース ビルドでそれを塗りつぶすことができるのであれば、なぜ抽象クラスを最初の場所?

于 2013-07-23T18:20:33.513 に答える
12

@propertyとを使用@dynamicすることもできます。動的プロパティを宣言し、対応するメソッドの実装を指定しない場合でも、すべてが警告なしでコンパイルされ、unrecognized selectorアクセスしようとすると実行時にエラーが発生します。これは基本的に を呼び出すのと同じことです[self doesNotRecognizeSelector:_cmd]が、入力がはるかに少なくなります。

于 2010-11-03T21:46:31.953 に答える
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");

私が言ったように、これは実際のコンパイラ保護ではありませんが、抽象メソッドをサポートしない言語で取得するのとほぼ同じです。

于 2012-11-06T13:38:59.293 に答える
7

質問への回答は、既に与えられた回答の下のコメントに散らばっています。したがって、ここでは要約して単純化しています。

オプション 1: プロトコル

実装なしで抽象クラスを作成する場合は、「プロトコル」を使用します。プロトコルを継承するクラスは、プロトコル内のメソッドを実装する義務があります。

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

オプション 2: テンプレート メソッド パターン

「テンプレート メソッド パターン」のような部分実装の抽象クラスを作成したい場合は、これが解決策です。 Objective-C - テンプレート メソッド パターン?

于 2014-03-27T10:59:33.737 に答える
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

于 2013-12-08T15:13:24.933 に答える
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_」部分を簡単に使用することはできません。

もちろん、拡張機能がオプションの場合、クラスはデフォルトの実装を持つことができます。しかし、他の回答が言うように、抽象(仮想)クラスのように、適切な場合は実行時例外を実装してください。

コメント/ドキュメントを掘り下げたり... 仮定したりするのではなく、スーパーの実装を事前/事後呼び出しするのが最適な場合のヒントでさえ、このようなコンパイラのヒントを組み込むとよいでしょう。

ヒントの例

于 2012-05-03T19:55:04.393 に答える
3

おそらく、この種の状況は開発時にのみ発生するはずなので、これでうまくいくかもしれません:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}
于 2013-04-26T03:27:56.883 に答える
2

Cocoaは抽象と呼ばれるものを提供しません。実行時にのみチェックされ、コンパイル時にはチェックされないクラス抽象を作成できます。

于 2014-10-29T18:18:43.020 に答える
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
于 2014-04-02T02:35:45.607 に答える
-3

デリゲートを作成することはできませんか?

デリゲートは、どの関数を定義する必要があるかを示すという意味で、抽象基本クラスに似ていますが、実際にはそれらを定義しません。

次に、デリゲート (つまり、抽象クラス) を実装するたびに、動作を定義する必要があるオプション関数と必須関数について、コンパイラから警告が表示されます。

これは私には抽象基本クラスのように思えます。

于 2014-05-22T14:09:22.220 に答える