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

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

У меня конкретно возникают проблемы с пониманием того, как работает следующий код (из Какао-программирование для 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 и Какао с примерно аналогичными концепциями .NET. Это похоже на концепцию делегата .NET, но нетипизировано?

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

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


person John Rudy    schedule 24.11.2008    source источник


Ответы (4)


Согласно справочнику Apple по классу NSInvocation:

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

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

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


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

[myArray addObject:myString];

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

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

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 для выполнения необходимого действия.

Дополнительные сведения см. В разделе Регистрация операций отмены.

person e.James    schedule 24.11.2008
comment
Одно небольшое исправление к отличному в остальном ответу ... вам нужно передать указатель на объекты в setArgument:atIndex:, поэтому присвоение arg должно фактически читать [myInvocation setArgument:&myString atIndex:2]. - person Ryan McCuaig; 03.11.2009
comment
@ Райан МакКуэйг: Спасибо, что указали на это. Я внес изменения и добавил ссылку на соответствующую документацию. - person e.James; 03.11.2009
comment
Чтобы прояснить примечание Райана, индекс 0 зарезервирован для себя, а индекс 1 зарезервирован для _cmd (см. Ссылку e.James, опубликованную для более подробной информации). Таким образом, ваш первый аргумент помещается в индекс 2, второй аргумент - в индекс 3 и т. Д. - person Dave; 12.07.2010
comment
Я честно не понимаю, зачем нам звонить - person haroldcampbell; 07.04.2011
comment
@haroldcampbell: как нам позвонить? - person e.James; 08.04.2011
comment
Я не понимаю, зачем нам вызывать setSelector, ведь мы уже указали селектор в mySignature. - person Gleno; 26.07.2013
comment
@Gleno: NSInvocation довольно гибкий. Фактически вы можете установить любой селектор, соответствующий сигнатуре метода, поэтому вам не обязательно использовать тот же селектор, который использовался для создания сигнатуры метода. В этом примере вы могли бы так же легко сделать setSelector: @selector (removeObject :), поскольку они используют одну и ту же сигнатуру метода. - person e.James; 27.07.2013
comment
Ага, значит в init ставится только подпись, которая создается из селектора, а не сам селектор. Очень интересно! - person Mercurial; 27.09.2013
comment
Я бы отдал 2 голоса за красивое объяснение на простом примере. Спасибо, Э. Джеймс. - person Tushar Vengurlekar; 30.06.2015
comment
Зачем вообще это нужно использовать? Как вы сказали, в «более поздний момент времени», почему бы просто не вызвать [object message] вместо этого вуду? Какие преимущества дает процесс предварительной подготовки сообщения? - person SexyBeast; 21.09.2015
comment
@Cupidvogel объект NSInvocation может храниться и / или использоваться совместно с другими объектами, при этом этим объектам не нужно знать детали реализации. Лучшим примером является NSUndoManager: контроллер, который выполняет действие do, точно знает, какую функцию вызывать и с какими аргументами для отмены, поэтому он создает объект NSInvocation и передает его NSUndoManager. NSUndoManager должен только вызвать invoke. Не нужно знать, что на самом деле делается в цели. - person e.James; 06.10.2015
comment
Разве вы не можете создать вызов с помощью всего NSInvocation* myInvocation = [[NSInvocation alloc] init];? Зачем нужно указывать селектор дважды? - person Ferran Maylinch; 01.12.2015
comment
К сожалению, ссылка «Использование NSInvocation» больше не действительна. :( - person Logicsaurus Rex; 13.04.2017

Вот простой пример 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];
person Dave    schedule 11.07.2010
comment
Интересно, что даже несмотря на то, что инициализатор invocationWithMethodSignature: используется, вам все равно нужно вызывать setSelector:. Это кажется лишним, но я только что протестировал, и это необходимо. - person ThomasW; 18.05.2012
comment
Это продолжает работать в бесконечном цикле? и что такое _cmd - person j2emanue; 28.03.2015

Вы можете попробовать просто использовать здесь библиотеку, которая намного удобнее: http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

person Casebash    schedule 02.02.2010

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

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

https://github.com/clearbrian/NSInvocation_Runtime

person brian.clear    schedule 28.05.2014