私はもともとJavaプログラマーで、現在Objective-Cを使用しています。抽象クラスを作成したいのですが、Objective-Cではそれができないようです。これは可能ですか?
そうでない場合、Objective-Cで抽象クラスにどのくらい近づくことができますか?
私はもともとJavaプログラマーで、現在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];
そのため、メソッドからreturnステートメントを追加する必要はありません。
抽象クラスが本当にインターフェースである(つまり、具体的なメソッド実装がない)場合は、Objective-Cプロトコルを使用する方が適切なオプションです。
@protocol
できますが、そこでメソッドを定義することはできません。
+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
これは非常にうまく機能します。
doesNotRecognizeSelector
。
いいえ、Objective-Cで抽象クラスを作成する方法はありません。
メソッド/セレクターがdoesNotRecognizeSelector:を呼び出すようにすることで、抽象クラスをモックできます。したがって、例外を発生させて、クラスを使用できなくします。
例えば:
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
initに対してもこれを行うことができます。
NSObject
リファレンスは、メソッドのオーバーライドを強制するのではなく、メソッドを継承したくない場合にこれを使用することを提案しています。同じことかもしれませんが、たぶん:)
上記の@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
ドキュメントを参照)。
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
。より多くの情報を提供します
私が思いついた解決策は:
このようにして、コンパイラーは、子クラスによって実装されていないプロトコルのメソッドに対して警告を表示します。
これはJavaほど簡潔ではありませんが、必要なコンパイラ警告が表示されます。
NSObject
)。その後、プロトコルと基本クラス宣言を同じヘッダーファイルに配置すると、抽象クラスとほとんど区別がつかなくなります。
現時点では、Objective-CにはJavaのような抽象的なコンパイラ構造はありません。
したがって、行うのは、抽象クラスを他の通常のクラスとして定義し、空であるか、セレクターの非サポートを報告する抽象メソッドのメソッドスタブを実装することだけです。例えば...
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
また、デフォルトの初期化子を介して抽象クラスが初期化されないようにするために、次のことも行います。
- (id)init
{
[self doesNotRecognizeSelector:_cmd];
[self release];
return nil;
}
抽象基本クラスを作成する代わりに、プロトコル(Javaインターフェースと同様)の使用を検討してください。これにより、一連のメソッドを定義し、プロトコルに準拠し、メソッドを実装するすべてのオブジェクトを受け入れることができます。たとえば、操作プロトコルを定義して、次のような機能を持つことができます。
- (void)performOperation:(id<Operation>)op
{
// do something with operation
}
opは、操作プロトコルを実装する任意のオブジェクトです。
メソッドを定義するだけではなく、抽象基本クラスが必要な場合は、通常のObjective-Cクラスを作成して、インスタンス化されないようにすることができます。-(id)init関数をオーバーライドして、nilまたはassert(false)を返すようにします。これはあまりクリーンなソリューションではありませんが、Objective-Cは完全に動的であるため、抽象基本クラスに直接相当するものは実際にはありません。
このスレッドは古いもので、共有したいもののほとんどはすでにここにあります。
しかし、私のお気に入りの方法は言及されていません。また、私の知る限り、現在のClangにはネイティブサポートがないので、ここに行きます…
まず、何よりも(他の人がすでに指摘しているように)抽象クラスはObjective-Cでは非常に珍しいものです。通常、代わりに(委任を通じて)構成を使用します。これはおそらく、@dynamic
CoreDataの導入に伴いObjC 2.0にIIRCが追加されたプロパティを除いて、そのような機能が言語/コンパイラにまだ存在しない理由です。
しかし、(状況を注意深く評価した後)委任(または一般的な構成)は問題の解決にあまり適していないという結論に達しました。ここでは、その方法を示します。
[self doesNotRecognizeSelector:_cmd];
…__builtin_unreachable();
、非voidメソッドに対して表示される警告を沈黙させ、「コントロールは戻りなしで非void関数の最後に到達しました」と通知します。-[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
ご覧のとおり、マクロは抽象メソッドの完全な実装を提供し、必要なボイラープレートの量を最小限に抑えます。
さらに良い選択肢は、 クランチームにロビー活動をすることです。機能をリクエストして、このケースにコンパイラー属性を提供するようことです。(これは、NSIncrementalStoreなどのサブクラス化するシナリオのコンパイル時診断も有効にするため、より良いです。)
__builtin_unreachable()
は人々を驚かせるかもしれませんが、それも理解するのに十分簡単です。)その最後の点にはいくつかの説明が必要だと思います:
一部(ほとんど?)の人は、リリースビルドでアサーションを削除します。(私はその習慣に同意しませんが、それは別の話です...)必要なメソッドの実装に失敗する—しかし—は悪い、ひどい、間違っている、そして基本的にあなたのプログラムの宇宙の終わりです。これは未定義であり、未定義の動作はこれまでで最悪の事態であるため、プログラムはこの点で正しく動作できません。したがって、新しい診断を生成せずにそれらの診断を取り除くことができることは完全に受け入れられないでしょう。
このようなプログラマエラーの適切なコンパイル時診断を取得できず、実行時にこれらのエラーを検出する必要があるのは十分に悪いことですが、リリースビルドでそれを解決できる場合は、なぜ抽象クラスを最初の場所?
__builtin_unreachable();
。gemはこれを完璧に機能させます。マクロは自己文書化し、オブジェクトで欠落しているメソッドを呼び出した場合の動作と一致します。
使用@property
して動作する@dynamic
こともできます。動的プロパティを宣言し、一致するメソッドの実装を指定しない場合でも、警告なしですべてがコンパイルされ、unrecognized selector
アクセスしようとすると実行時にエラーが発生します。これはを呼び出すのと基本的に同じです[self doesNotRecognizeSelector:_cmd]
が、タイピングがはるかに少なくなります。
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");
先ほど言ったように、これは実際のコンパイラ保護ではありませんが、抽象メソッドをサポートしない言語を使用するのとほぼ同じです。
-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
また、プロトコルやその他のソリューションを使用することもできますが、これは最も簡単な方法の1つです。
Cocoaは抽象と呼ばれるものを提供していません。実行時にのみチェックされるクラスアブストラクトを作成できますが、コンパイル時にはチェックされません。
私は通常、抽象化したいクラスの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
がありますが、そのクラスのインスタンスを必要とするすべてのメソッドは、実際にタイプを要求し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
デリゲートを作成できませんか?
デリゲートは、定義する必要がある関数を言うという意味では抽象基本クラスに似ていますが、実際には定義しません。
次に、デリゲート(つまり、抽象クラス)を実装するたびに、動作を定義する必要があるオプション関数と必須関数についてコンパイラーから警告を受けます。
これは抽象基底クラスのように思えます。