NSInvocation for Dummies?


139

正確にはどのように機能しNSInvocationますか?良い紹介はありますか?

以下のコード(Cocoaプログラミングfor Mac OS X、第3版)がどのように機能するかを理解するのに問題がありますが、チュートリアルのサンプルとは別に概念を適用することもできます。コード:

- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index
{
    NSLog(@"adding %@ to %@", p, employees);
    // Add inverse of this operation to undo stack
    NSUndoManager *undo = [self undoManager];
    [[undo prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index];
    if (![undo isUndoing])
        [undo setActionName:@"Insert Person"];

    // Finally, add person to the array
    [employees insertObject:p atIndex:index];
}

- (void)removeObjectFromEmployeesAtIndex:(int)index
{
    Person *p = [employees objectAtIndex:index];
    NSLog(@"removing %@ from %@", p, employees);
    // Add inverse of this operation to undo stack
    NSUndoManager *undo = [self undoManager];
    [[undo prepareWithInvocationTarget:self] insertObject:p
                                       inEmployeesAtIndex:index];
    if (![undo isUndoing])
        [undo setActionName:@"Delete Person"];

    // Finally, remove person from array
    [employees removeObjectAtIndex:index];
}

私はそれが何をしようとしているのかを理解しています。(ところで、employeesあるNSArrayカスタムのPersonクラスです。)

私は.NETの男なので、おなじみのないObj-CおよびCocoaの概念を、ほぼ類似した.NETの概念に関連付けようとします。これは.NETのデリゲートコンセプトに似ていますが、型付けされていませんか?

これは本から100%明確ではないので、本当のCocoa / Obj-Cエキスパートから補足的なものを探しています。ここでも、シンプルな(-ish)例の下の基本的な概念を理解することを目標としています。私は実際に独立して知識を適用できるようになりたいと思っています-9章までは、それを行うのに問題はありませんでした。でも今 ...

前もって感謝します!

回答:


284

よると、AppleのNSInvocationクラス参照

NSInvocationObjective-Cのメッセージのレンダリング静的であること、それは対象になってアクションです。

そして、もう少し詳しく:

メッセージの概念は、objective-cの哲学の中心です。メソッドを呼び出すとき、またはオブジェクトの変数にアクセスするときは常に、メッセージを送信します。NSInvocation別の時点でオブジェクトにメッセージを送信したり、同じメッセージを数回送信したりする場合に便利です。送信するメッセージNSInvocation記述し、後でそれを呼び出す(実際にターゲットオブジェクトに送信する)ことができます。


たとえば、配列に文字列を追加するとします。通常はaddObject:次のようにメッセージを送信します。

[myArray addObject:myString];

ここで、を使用NSInvocationして、別の時点でこのメッセージを送信するとします。

まず、のセレクターNSInvocationで使用するオブジェクトを準備します。NSMutableArrayaddObject:

NSMethodSignature * mySignature = [NSMutableArray
    instanceMethodSignatureForSelector:@selector(addObject:)];
NSInvocation * myInvocation = [NSInvocation
    invocationWithMethodSignature:mySignature];

次に、メッセージを送信するオブジェクトを指定します。

[myInvocation setTarget:myArray];

そのオブジェクトに送信するメッセージを指定します。

[myInvocation setSelector:@selector(addObject:)];

そのメソッドの引数を入力します。

[myInvocation setArgument:&myString atIndex:2];

オブジェクトの引数はポインタで渡す必要があることに注意してください。指摘してくれたRyan McCuaigに感謝します。詳細については、Appleのドキュメントを参照してください。

この時点で、myInvocationは送信可能なメッセージを記述する完全なオブジェクトです。実際にメッセージを送信するには、次を呼び出します。

[myInvocation invoke];

この最後のステップにより、メッセージが送信され、基本的にが実行され[myArray addObject:myString];ます。

メールを送信するようなものだと考えてください。新しい電子メール(NSInvocationオブジェクト)を開き、送信先の人(オブジェクト)のアドレスを入力し、受信者へのメッセージを入力し(a selectorと引数を指定)、[送信]をクリックします(呼び出しinvoke)。

詳細については、NSInvocationの使用を参照してください。上記が機能しない場合は、NSInvocationの使用を参照してください。


NSUndoManagerコマンドNSInvocation反転できるようにオブジェクトを使用します。基本的に、あなたがしていることは、NSInvocation言うオブジェクトを作成することです:「ねえ、私が今したことを元に戻したいなら、これらの引数を付けて、このメッセージをそのオブジェクトに送ってください」。NSInvocationオブジェクトをに渡すと、NSUndoManagerそのオブジェクトが取り消し可能なアクションの配列に追加されます。ユーザーが「元に戻す」NSUndoManagerを呼び出すと、配列内の最新のアクションを検索し、格納されているNSInvocationオブジェクトを呼び出して必要なアクションを実行します。

詳細については、取り消し操作の登録を参照してください。


10
それ以外の点では優れた答えに対する1つのマイナーな修正...でオブジェクトへのポインターを渡す必要があるsetArgument:atIndex:ため、引数の割り当ては実際に読み取る必要があります[myInvocation setArgument:&myString atIndex:2]
Ryan McCuaig 2009年

60
ライアンのメモを明確にするために、インデックス0は「自己」用に予約され、インデックス1は「_cmd」用に予約されています(詳細については、リンクe.Jamesを参照してください)。したがって、最初の引数はインデックス2に配置され、2番目の引数はインデックス3に配置されます...
Dave

4
@haroldcampbell:何を呼ばなければならないのですか?
e.James、2011

6
mySignatureでセレクターを既に指定しているため、なぜsetSelectorを呼び出さなければならないのか理解できません。
グレノ2013

6
@Gleno:NSInvocationは非常に柔軟です。メソッドシグネチャに一致するセレクターを実際に設定できるため、メソッドシグネチャの作成に使用したセレクターを必ずしも使用する必要はありません。この例では、同じメソッドシグネチャを共有しているため、setSelector:@selector(removeObject :)を簡単に実行できます。
e.James、2013

48

以下は、実行中のNSInvocationの簡単な例です。

- (void)hello:(NSString *)hello world:(NSString *)world
{
    NSLog(@"%@ %@!", hello, world);

    NSMethodSignature *signature  = [self methodSignatureForSelector:_cmd];
    NSInvocation      *invocation = [NSInvocation invocationWithMethodSignature:signature];

    [invocation setTarget:self];                    // index 0 (hidden)
    [invocation setSelector:_cmd];                  // index 1 (hidden)
    [invocation setArgument:&hello atIndex:2];      // index 2
    [invocation setArgument:&world atIndex:3];      // index 3

    // NSTimer's always retain invocation arguments due to their firing delay. Release will occur when the timer invalidates itself.
    [NSTimer scheduledTimerWithTimeInterval:1 invocation:invocation repeats:NO];
}

呼び出されると[self hello:@"Hello" world:@"world"];、メソッドは次のようになります。

  • 「Hello world!」を印刷します
  • それ自身のためにNSMethodSignatureを作成します。
  • NSInvocationを作成してデータを投入し、自分自身を呼び出します。
  • NSInvocationをNSTimerに渡します
  • タイマーは(約)1秒で起動し、元の引数を使用してメソッドが再度呼び出されます。
  • 繰り返す。

最後に、次のような印刷結果が得られます。

2010-07-11 17:48:45.262 Your App[2523:a0f] Hello world!
2010-07-11 17:48:46.266 Your App[2523:a0f] Hello world!
2010-07-11 17:48:47.266 Your App[2523:a0f] Hello world!
2010-07-11 17:48:48.267 Your App[2523:a0f] Hello world!
2010-07-11 17:48:49.268 Your App[2523:a0f] Hello world!
2010-07-11 17:48:50.268 Your App[2523:a0f] Hello world!
2010-07-11 17:48:51.269 Your App[2523:a0f] Hello world!
...

もちろん、selfNSTimerがNSInvocationを送信するためには、ターゲットオブジェクトが存在し続ける必要があります。たとえば、シングルトンオブジェクト、またはアプリケーションの実行中に存在するAppDelegate。


更新:

上記のように、NSInvocationを引数としてNSTimerに渡すと、NSTimerは自動的にすべてのNSInvocationの引数を保持します。

NSTimerの引数としてNSInvocationを渡さず、しばらく保持することを計画している場合は、その-retainArgumentsメソッドを呼び出す必要があります。そうしないと、呼び出しが呼び出される前に引数が割り当て解除され、最終的にコードがクラッシュする可能性があります。方法は次のとおりです。

NSMethodSignature *signature  = ...;
NSInvocation      *invocation = [NSInvocation invocationWithMethodSignature:signature];
id                arg1        = ...;
id                arg2        = ...;

[invocation setTarget:...];
[invocation setSelector:...];
[invocation setArgument:&arg1 atIndex:2];
[invocation setArgument:&arg2 atIndex:3];

[invocation retainArguments];  // If you do not call this, arg1 and arg2 might be deallocated.

[self someMethodThatInvokesYourInvocationEventually:invocation];

6
invocationWithMethodSignature:イニシャライザが使用されていても、を呼び出す必要があるのは興味深いことですsetSelector:。それは冗長に思えますが、私はテストしただけで、それは必要です。
ThomasW

これは無限ループで実行し続けますか?_cmdとは
j2emanue


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.