委任にはさまざまな用途があります。 たとえば、継承なしの別のクラスでのいくつかの動作の実装。 別の委任は、通知を送信する方法として使用されます。 たとえば、UITextFieldはデリゲートでtextFieldDidEndEditing:メソッドを呼び出し、編集が終了したことなどを通知します。
ここで、タスクを想像してください。委任者にメッセージを1人の委任者ではなく複数の委任者に送信させる必要があり、委任はプロパティを介した標準メソッドによって実装されます。
例
例は少し引き出されていますが、まだです。
入力されたテキストをチェックするカスタムUITextFieldを作成する必要があります。テキストが無効な場合、コントロールの色が変わります。 さらに、ユーザーが指定された数の文字のみを入力できることを確認する必要があります。
つまり、次のようなものが必要です。
@protocol PTCTextValidator <NSObject> - (BOOL)textIsValid:(NSString *)text; - (BOOL)textLengthIsValid:(NSString *)text; @end @interface PTCVerifiableTextField : UITextField @property (nonatomic, weak) IBOutlet id<PTCTextValidator> validator; @property (nonatomic, strong) UIColor *validTextColor UI_APPEARANCE_SELECTOR; @property (nonatomic, strong) UIColor *invalidTextColor UI_APPEARANCE_SELECTOR; @property (nonatomic, readonly) BOOL isValid; @end
そして、ここで問題が発生します。 PTCVerifiableTextFieldがカスタム動作を実装するには、スーパークラス(UITextField)のデリゲートである必要があります。 ただし、これを行うと、外部からデリゲートプロパティに触れることができなくなります。
つまり 以下のコードはPTCVerifiableTextFieldの内部ロジックを破壊します
PTCVerifiableTextField *textField = [PTCVerifiableTextField alloc] initWIthFrame:CGrectMake(0, 0, 100 20)]; textField.delegate = self; [self.view addSubview:textField];
したがって、問題を取得します。プロパティを作成するには
@property(nonatomic, assign) id<UITextFieldDelegate> delegate
複数のオブジェクトを割り当てることができました。
解決策
ソリューションはそれ自体を提案します。 いくつかのデリゲートとメッセージを格納するコンテナを作成する必要がありますが、このデリゲートとメッセージはコンテナに格納されたオブジェクトには格納されません。
つまり、格納されている要素へのリクエストをプロキシするコンテナが必要です。
このソリューションには1つの大きな欠点があります-委任された関数が値を返す場合、戻り値と見なすデリゲートを呼び出した結果を何らかの方法で決定する必要があります。
そのため、何かをプロキシする前に、メッセージ転送とNSProxyを理解する必要があります。
メッセージ転送
Objective-Cはメッセージを処理します。 オブジェクトのメソッドを呼び出しません。 代わりに、私たちは彼にメッセージを送ります。 したがって、メッセージ転送とは、メッセージを別のオブジェクトにリダイレクトすることです。 そのプロキシ。
応答しないメッセージにオブジェクトを送信するとエラーが発生することに注意してください。 ただし、エラーが生成される前に、ランタイムはオブジェクトにメッセージを処理するもう1つの機会を与えます。
オブジェクトにメッセージを送信するとどうなるか見てみましょう。
1.オブジェクトがメソッドを実装する場合、つまりIMPを取得できる場合(たとえば、
method_getImplementation(class_getInstanceMethod(subclass, aSelecor))
)、ランタイムはメソッドを呼び出します。 それ以外の場合は先へ進みます
2.
+(BOOL)resolveInstanceMethod:(SEL)aSEL
または
+(BOOL)resolveClassMethod:(SEL)name
がヘルメットがクラスへのメッセージである場合に呼び出されます。 このメソッドにより、目的のセレクタを動的に追加できます。 YESが返された場合、ランタイムは再びIMPを取得してメソッドを呼び出します。 それ以外の場合は先へ進みます
このメソッドは、セレクターが実装されていない場合、
+(BOOL)respondsToSelector:(SEL)aSelector
および
+(BOOL)instancesRespondToSelector:(SEL)aSelector
れます。 さらに、このメソッドはセレクターごとに1回だけ呼び出され、メソッドを追加する機会は二度とありません!
メソッドを動的に追加する例:
+ (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSel]; }
3.いわゆる早送りが実行されます。 つまり、メソッド
-(id)forwardingTargetForSelector:(SEL)aSelector
呼び出されます
-(id)forwardingTargetForSelector:(SEL)aSelector
このメソッドは、現在のオブジェクトの代わりに使用するオブジェクトを返します。 一般に、多重継承をシミュレートするのに非常に便利なことです。 この段階では、NSInvoacationを作成せずに転送を実行できるため、高速です。
このメソッドによって返されるオブジェクトに対して、すべての手順が繰り返されます。 ドキュメントによると、自己を返すと、無限ループが発生します。 実際には、無限ループは発生しません。明らかに、ランタイムは修正されています。
4.前の2つの手順は、転送の最適化です。 その後、ランタイムはNSInvocationを作成します。
NSInvocationランタイムの作成は次のようになります。
NSMethodSignature *sig = ... NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig]; [inv setSelector:selector];
つまり、NSInvocationを作成するには、ランタイムがメソッドシグネチャ(NSMethodSignature)を取得する必要があります。 したがって、オブジェクトは
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
ます。 メソッドがNSMethodSignatureではなくnilを返す場合、ランタイムはオブジェクトを呼び出します
-(void)doesNotRecognizeSelector:(SEL)aSelector
、つまり クラッシュが発生します。
NSMethodSignatureは、次の方法で作成できます。
- メソッドを使用
+(NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
クラスがプロトコル(@interface MyClass : NSObject ), nil, .
実装することを宣言しただけの場合、@interface MyClass : NSObject ), nil, .
ことに注意してください@interface MyClass : NSObject ), nil, .
@interface MyClass : NSObject ), nil, .
メソッドを使用-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
内部呼び出し[[self class] instanceMethodSignatureForSelector:...]
NSMethodSignatureクラスに属する+(NSMethodSignature *)signatureWithObjCTypes:(const char *)types
メソッドを使用し、NSMethodSignatureを自分でアセンブルします。
。