НСВП для манекенів?


139

Як саме 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];
}

Я отримую те, що намагається зробити. (BTW employees- це NSArrayспеціальний Personклас.)

Як хлопець .NET, я намагаюся пов’язати незнайомі поняття Obj-C та какао з приблизно аналогічними поняттями .NET. Це схоже на делегатську концепцію .NET, але нетипізовано?

Це не на 100% зрозуміло з книги, тому я шукаю щось додаткове від справжніх фахівців з какао / Obj-C, знову ж таки з метою зрозуміти основоположну концепцію під простим прикладом. Мені дуже хочеться самостійно застосувати знання - до 9-го розділу, мені це не склалося труднощів. Але зараз ...

Спасибі заздалегідь!

Відповіді:


284

Згідно з посиланням на клас NSInvocation від Apple :

Повідомлення NSInvocationObjective-C, надане статичним, тобто це дія, перетворена на об'єкт.

І, трохи детальніше:

Поняття повідомлень є центральним у філософії об'єктивних с. Кожного разу, коли ви зателефонуєте до методу чи отримаєте доступ до змінної якогось об'єкта, ви надсилаєте йому повідомлення. NSInvocationстане в нагоді, коли ви хочете відправити повідомлення об’єкту в інший момент часу або надіслати одне і те ж повідомлення кілька разів. NSInvocationдозволяє описати повідомлення, яке ви збираєтеся надіслати, а потім викликати його (фактично надіслати його цільовому об’єкту) згодом.


Наприклад, скажімо, що ви хочете додати рядок до масиву. Ви зазвичай відправляєте addObject:повідомлення таким чином:

[myArray addObject:myString];

Скажімо, ви хочете використовувати NSInvocationце повідомлення для надсилання у якийсь інший момент часу:

По-перше, ви підготували б NSInvocation об'єкт для використання із селектором NSMutableArrays 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:, щоб призначення аргументу повинне було насправді читати [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig

60
Просто для уточнення примітки Райана, індекс 0 зарезервований для "self", а індекс 1 - "_cmd" (див. Посилання e.James, розміщене для більш детальної інформації). Тож ваш перший аргумент розміщується в індексі 2, другий аргумент - в індексі 3 тощо ...
Дейв,

4
@haroldcampbell: як нам зателефонувати?
e.James

6
Я не розумію, чому нам потрібно викликати setSelector, оскільки ми вже вказали селектор у MySignature.
Глено

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"];- метод:

  • Друк "Привіт, світ!"
  • Створіть 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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.