NSInvocation для чайников?

Как работает NSInvocation? Есть ли хорошее представление?

В частности, у меня возникают проблемы с пониманием того, как работает следующий код (от Cocoa Programming for Mac OS X, 3rd Edition), но затем также можно применять понятия независимо от образца учебника. Код:

- (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];
}

Я получаю то, что он пытается сделать. (BTW, employees является NSArray пользовательского класса Person.)

Будучи человеком .NET, я пытаюсь связать незнакомые концепции Obj-C и Cocoa с примерно аналогичными понятиями .NET. Это похоже на концепцию делегата .NET, но нетипизируется?

Это не 100% ясно из книги, поэтому я ищу что-то дополнительное от реальных экспертов Cocoa/Obj-C, опять же с целью, чтобы я понял фундаментальную концепцию под простым (-ish) примером. Я действительно стараюсь самостоятельно применять знания до 9-й главы, мне это не сложно. Но теперь...

Спасибо заранее!

Ответ 1

Согласно справке класса Apple NSInvocation:

NSInvocation - это сообщение Objective C, отображаемое как статическое, то есть это действие, превращенное в объект.

И немного подробнее:

Концепция сообщений является центральной в философии цели. Каждый раз, когда вы вызываете метод или обращаетесь к переменной какого-либо объекта, вы отправляете ему сообщение. NSInvocation пригодится, когда вы хотите отправить сообщение объекту в другой момент времени или отправить одно и то же сообщение несколько раз. NSInvocation позволяет вам описать сообщение, которое вы собираетесь отправить, а затем вызвать его (фактически отправить его целевому объекту) позже.


Например, допустим, вы хотите добавить строку в массив. Обычно вы отправляете сообщение addObject: следующим образом:

[myArray addObject:myString];

Теперь, допустим, вы хотите использовать NSInvocation для отправки этого сообщения в другой момент времени:

Сначала вы должны подготовить объект NSInvocation для использования с NSMutableArray addObject: selector:

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

Далее вы должны указать, на какой объект отправить сообщение:

[myInvocation setTarget:myArray];

Укажите сообщение, которое вы хотите отправить этому объекту:

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

И заполните любые аргументы для этого метода:

[myInvocation setArgument:&myString atIndex:2];

Обратите внимание, что аргументы объекта должны передаваться указателем. Спасибо Райану МакКуайгу за указание на это, и, пожалуйста, смотрите документацию Apple для получения более подробной информации.

На этом этапе myInvocation представляет собой законченный объект, описывающий сообщение, которое можно отправить. Чтобы действительно отправить сообщение, вы должны позвонить:

[myInvocation invoke];

Этот последний шаг приведет к отправке сообщения, по существу, выполняющего [myArray addObject:myString]; ,

Думайте об этом как об отправке электронного письма. Вы открываете новое электронное письмо (объект NSInvocation), NSInvocation адрес человека (объекта), которому хотите его отправить, вводите сообщение для получателя (укажите selector и аргументы), а затем нажимаете "отправить" (вызов invoke).

Посмотрите Используя NSInvocation для получения дополнительной информации. Посмотрите Используя NSInvocation, если вышеупомянутое не работает.


NSUndoManager использует объекты NSInvocation чтобы он мог NSInvocation команды. По сути, вы NSInvocation объект NSInvocation который говорит: "Эй, если вы хотите отменить то, что я только что сделал, отправьте это сообщение этому объекту с этими аргументами". Вы NSInvocation объект NSUndoManager, и он добавляет этот объект в массив отменяемых действий. Если пользователь вызывает "Undo", NSUndoManager просто ищет самое последнее действие в массиве и вызывает сохраненный объект NSInvocation для выполнения необходимого действия.

См. Регистрация операций отмены для получения дополнительной информации.

Ответ 2

Вот простой пример 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 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"]; - метод будет:

  • Печать "Привет, мир!"
  • Создайте 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!
...

Конечно, целевой объект self должен продолжать существовать, чтобы NSTimer отправил ему NSInvocation. Например, объект Singleton или AppDelegate, который существует на протяжении всего приложения.


UPDATE:

Как отмечалось выше, когда вы передаете NSInvocation в качестве аргумента NSTimer, NSTimer автоматически сохраняет все аргументы NSInvocation.

Если вы не передаете NSInvocation в качестве аргумента для NSTimer и планируете некоторое время придерживаться его, вы должны вызвать его метод -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];

Ответ 4

Я создаю простой пример вызова различных типов методов с помощью NSInvocation.

У меня возникли проблемы с вызовом нескольких параметров с помощью obj_msgSend

https://github.com/clearbrian/NSInvocation_Runtime