Objective-Cプロトコルのデフォルトの実装を提供するにはどうすればよいですか?
質問
オプションのルーチンを備えたObjective-Cプロトコルを指定したいと思います。ルーチンがプロトコルに準拠するクラスによって実装されていない場合、その場所でデフォルトの実装を使用したいと思います。プロトコル自体に、このデフォルトの実装を定義できる場所はありますか?そうでない場合、このデフォルトの実装を至る所にコピーして貼り付けるためのベストプラクティスは何ですか?
解決
Objective-Cプロトコルは、デフォルトの実装に対してアフォーダンスがありません。これらは、他のクラスで実装できるメソッド宣言の純粋なコレクションです。 Objective -Cの標準的なプラクティスは、実行時にオブジェクトをテストして、そのメソッドを呼び出す前に指定されたセレクターに応答するかどうかを確認することです。 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]];
プロトコルに準拠しているクラスの初期化とすべてのデフォルトメソッドが追加されます。
本当に魅力的な方法は、ランタイムを使用することです。スタートアップでは、プログラムの実行の非常に早い段階で、以下を実行します。
- すべてのクラスを列挙し、プロトコルを実装するクラスを見つけます
- クラスがメソッドを実装するかどうかを確認します
- そうでない場合は、デフォルトの実装をクラスに追加します
それほど問題なく達成することができます。
Ryanがプロトコルにデフォルトの実装がないと述べているため、スーパークラスで実装するもう1つのオプションは、デフォルトの実装を提供するクラスに含めることができる「ハンドラー」種類のクラスを実装することです。デフォルトのハンドラーの実装。
最終的に、メソッドのデフォルトの実装があるマクロを作成しました。
プロトコルのヘッダーファイルで定義しましたが、各実装の1ライナーにすぎません。
このようにして、いくつかの場所を実装する必要はなく、コンパイル時間で行われているため、実行時の魔法は必要ありません。
「WM」に同意します。非常に優れたソリューションは、すべてのデフォルトの実装をインターフェイスに(プロトコルと同じ名前の)に配置することです。サブクラスの「+初期化」メソッドでは、デフォルトのインターフェイスから実装されていないメソッドをそれ自体にコピーするだけです。
次のヘルパー機能が私のために機能しました
#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の欠落方法についての警告を提供しますが、それらを無視できます。
この手法は、ivarsではなくメソッドのみをコピーします。メソッドが存在しないデータメンバーにアクセスしている場合、奇妙なバグが得られる可能性があります。データがコードと互換性があることを確認する必要があります。 FoobarからMyProtocolまでReintrepret_castを行ったかのようです。