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

140

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

У меня конкретно возникают проблемы с пониманием того, как работает следующий код (из Cocoa Programming для 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];
}

Я понимаю, что он пытается сделать. (Кстати, employeesэто NSArrayособый Personкласс.)

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

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

Заранее спасибо!

Джон Руди
источник

Ответы:

285

Согласно ссылке на класс Apple NSInvocation :

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

И чуть подробнее:

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


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

[myArray addObject:myString];

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

Во- первых, вы должны подготовить NSInvocationобъект для использования с NSMutableArray«S addObject:селектора:

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объект), вводите адрес человека (объекта), которому вы хотите его отправить, вводите сообщение для получателя (укажите selectorи аргументы), а затем нажимаете «отправить» (позвоните invoke).

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


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

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

е. Джеймс
источник
10
Одно небольшое исправление к отличному в остальном ответу ... вам нужно передать указатель на объекты setArgument:atIndex:, поэтому назначение arg должно фактически читать [myInvocation setArgument:&myString atIndex:2].
Райан МакКуэйг
60
Чтобы прояснить примечание Райана, индекс 0 зарезервирован для «self», а индекс 1 зарезервирован для «_cmd» (см. Ссылку e.James, опубликованную для более подробной информации). Таким образом, ваш первый аргумент помещается в индекс 2, второй аргумент - в индекс 3 и т. Д.
Дэйв
4
@haroldcampbell: как нам позвонить?
e.James 07
6
Я не понимаю, почему мы должны вызывать setSelector, поскольку мы уже указали селектор в mySignature.
Gleno
6
@Gleno: NSInvocation довольно гибкий. Фактически вы можете установить любой селектор, соответствующий сигнатуре метода, поэтому вам не обязательно использовать тот же селектор, который использовался для создания сигнатуры метода. В этом примере вы могли бы так же легко сделать setSelector: @selector (removeObject :), поскольку они используют одну и ту же сигнатуру метода.
e.James
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!
...

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


ОБНОВИТЬ:

Как отмечалось выше, когда вы передаете 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];
Дэйв
источник
6
Интересно, что даже если invocationWithMethodSignature:инициализатор используется, вам все равно нужно позвонить setSelector:. Это кажется лишним, но я только что протестировал, и это необходимо.
ThomasW
Продолжает ли это работать в бесконечном цикле? и что такое _cmd
j2emanue
0

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

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

https://github.com/clearbrian/NSInvocation_Runtime

brian.clear
источник