オプションのルーチンでObjective-Cプロトコルを指定したいのですが。ルーチンがプロトコルに準拠するクラスによって実装されていない場合は、代わりにデフォルトの実装を使用したいと思います。このデフォルトの実装を定義できる場所はプロトコル自体にありますか?そうでない場合は、このデフォルトの実装をあちこちにコピーして貼り付けることを減らすためのベストプラクティスは何ですか?
6 に答える
Objective-Cプロトコルには、デフォルトの実装に対する余裕がありません。これらは、他のクラスで実装できる純粋なメソッド宣言のコレクションです。Objective-Cの標準的な方法は、実行時にオブジェクトをテストして、-[NSObject replysToSelector:]を使用して、そのメソッドを呼び出す前に、指定されたセレクターに応答するかどうかを確認することです。eオブジェクトが指定されたセレクターに応答しない場合、メソッドは呼び出されません。
探している結果を達成する1つの方法は、呼び出し元のクラスで探しているデフォルトの動作をカプセル化するメソッドを定義し、オブジェクトがテストに合格しなかった場合にそのメソッドを呼び出すことです。
別のアプローチは、プロトコルでメソッドを必須にし、特定の実装を提供したくないクラスのスーパークラスにデフォルトの実装を提供することです。
おそらく他のオプションもありますが、一般的に言って、Objective-Cには特定の標準的な方法はありません。ただし、上記の最初の段落で、オブジェクトによって実装されていない場合は、指定されたメソッドを呼び出さないことを除いては。
プロトコルは実装を定義するべきではないため、これを行うための標準的な方法はありません。
Objective-Cにはきちんとしたランタイムが付属しているので、もちろん、そのようにする必要があると本当に思っている場合は、そのような動作を追加できます(継承で同じことを実現することによって可能性はありません)。
MyProtocolを宣言したとします。次に、プロトコル宣言の下の.hファイルに同じ名前のインターフェイスを追加します。
@interface MyProtocol : NSObject <MyProtocol>
+ (void)addDefaultImplementationForClass:(Class)conformingClass;
@end
そして、対応する実装ファイルを作成します(ここでは読みやすくするためにMAObjCRuntimeを使用していますが、標準のランタイム関数はそれほど多くのコードではありません)。
@implementation MyProtocol
+ (void)addDefaultImplementationForClass:(Class)conformingClass {
RTProtocol *protocol = [RTProtocol protocolWithName:@"MyProtocol"];
// get all optional instance methods
NSArray *optionalMethods = [protocol methodsRequired:NO instance:YES];
for (RTMethod *method in optionalMethods) {
if (![conformingClass rt_methodForSelector:[method selector]]) {
RTMethod *myMethod = [self rt_methodForSelector:[method selector]];
// add the default implementation from this class
[conformingClass rt_addMethod:myMethod];
}
}
}
- (void)someOptionalProtocolMethod {
// default implementation
// will be added to any class that calls addDefault...: on itself
}
その後、あなたはただ電話する必要があります
[MyProtocol addDefaultImplementationForClass:[self class]];
プロトコルに準拠するクラスのイニシャライザーに、すべてのデフォルトメソッドが追加されます。
本当に魅力的な方法は、ランタイムを使用することです。プログラム実行の非常に早い段階で、起動時に次の手順を実行します。
- すべてのクラスを列挙し、プロトコルを実装するクラスを見つけます
- クラスがメソッドを実装しているかどうかを確認します
- そうでない場合は、デフォルトの実装をクラスに追加します
それはそれほど問題なく達成することができます。
私は「wm」に同意します。非常に優れた解決策は、すべてのデフォルトの実装を(プロトコルと同じ名前の)インターフェースに入れることです。サブクラスの「+initialize」メソッドでは、実装されていないメソッドをデフォルトのインターフェースからそれ自体にコピーするだけです。
次のヘルパー関数は私のために働いた
#import <objc/runtime.h>
// Get the type string of a method, such as "v@:".
// Caller must allocate sufficent space. Result is null terminated.
void getMethodTypes(Method method, char*result, int maxResultLen)
{
method_getReturnType(method, result, maxResultLen - 1);
int na = method_getNumberOfArguments(method);
for (int i = 0; i < na; ++i)
{
unsigned long x = strlen(result);
method_getArgumentType(method, i, result + x, maxResultLen - 1 - x);
}
}
// This copies all the instance methods from one class to another
// that are not already defined in the destination class.
void copyMissingMethods(Class fromClass, Class toClass)
{
// This gets the INSTANCE methods only
unsigned int numMethods;
Method* methodList = class_copyMethodList(fromClass, &numMethods);
for (int i = 0; i < numMethods; ++i)
{
Method method = methodList[i];
SEL selector = method_getName(method);
char methodTypes[50];
getMethodTypes(method, methodTypes, sizeof methodTypes);
if (![toClass respondsToSelector:selector])
{
IMP methodImplementation = class_getMethodImplementation(fromClass, selector);
class_addMethod(toClass, selector, methodImplementation, methodTypes);
}
}
free(methodList);
}
次に、次のようなクラス初期化子で呼び出します...
@interface Foobar : NSObject<MyProtocol>
@end
@implementation Foobar
+(void)initialize
{
// Copy methods from the default
copyMissingMethods([MyProtocol class], self);
}
@end
Xcodeは、Foobarの欠落しているメソッドに関する警告を表示しますが、無視してかまいません。
この手法では、メソッドのみがコピーされ、ivarはコピーされません。メソッドが存在しないデータメンバーにアクセスしている場合、奇妙なバグが発生する可能性があります。データがコードと互換性があることを確認する必要があります。これは、FoobarからMyProtocolにreinterpret_castを実行したかのようです。
Ryanがプロトコルのデフォルトの実装はないと述べているように、スーパークラスに実装する別のオプションは、デフォルトの実装を提供する任意のクラスに含めることができる「ハンドラー」の種類のクラスを実装することです。その後、適切なメソッドが呼び出しますデフォルトのハンドラーの実装。
メソッドのデフォルトの実装を持つマクロを作成することになりました。
プロトコルのヘッダーファイルで定義しましたが、各実装で1つのライナーにすぎません。
このように、実装をいくつかの場所で変更する必要はなく、コンパイル時に行われるため、実行時の魔法は必要ありません。