iOS - Як реалізувати perforSelector з кількома аргументами та з afterDelay?


90

Я новачок у iOS. У мене є метод селектора наступним чином -

- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

Я намагаюся реалізувати щось подібне -

[self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second" afterDelay:15.0];

Але це видає мені помилку, кажучи -

Instance method -performSelector:withObject:withObject:afterDelay: not found

Будь-які ідеї щодо того, чого мені не вистачає?

Відповіді:


142

Особисто я вважаю, що ближчим вирішенням ваших потреб є використання NSInvocation.

Щось на зразок наступного зробить роботу:

indexPath і dataSource - це дві змінні екземпляра, визначені одним і тим же методом.

SEL aSelector = NSSelectorFromString(@"dropDownSelectedRow:withDataSource:");

if([dropDownDelegate respondsToSelector:aSelector]) {
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[dropDownDelegate methodSignatureForSelector:aSelector]];
    [inv setSelector:aSelector];
    [inv setTarget:dropDownDelegate];

    [inv setArgument:&(indexPath) atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
    [inv setArgument:&(dataSource) atIndex:3]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation

    [inv invoke];
}

2
Домовились. Це повинна бути правильна відповідь. Дуже корисне рішення. Особливо в моєму випадку, коли не дозволяється змінювати підпис методу, що містить кілька аргументів.
AbhijeetMishra

Це виглядає як чудове рішення. Чи є спосіб отримати повернене значення з методу, який викликається за допомогою цієї техніки?
Девід Петтігрю,

15
Як ви визначаєте затримку за допомогою цієї техніки?
death_au

4
@death_au, просто замість invoke, зателефонуйте: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; я повинен погодитися, що це чудове рішення. Щасливого кодування!
Максим Четруска

2
Пізно до розмови, але є питання. Що таке dropDownDelegate?
Мінестроне-суп

98

Тому що не існує такого поняття, як [NSObject performSelector:withObject:withObject:afterDelay:]метод.

Вам потрібно інкапсулювати дані, які ви хочете надіслати, у якийсь єдиний об’єкт Objective C (наприклад, NSArray, NSD Dictionary, якийсь власний тип Objective C), а потім передати його за допомогою [NSObject performSelector:withObject:afterDelay:]добре відомого та улюбленого методу.

Наприклад:

NSArray * arrayOfThingsIWantToPassAlong = 
    [NSArray arrayWithObjects: @"first", @"second", nil];

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];

Я не отримую повідомлення про помилку, якщо видаляю параметр afterDelay. Чи означає це, що afterDelay не можна використовувати з кількома параметрами?
Сучі

1
ви не отримуєте помилку, але я б поспорився, що ви отримаєте виняток "селектор не знайдений" під час запуску (і те, що ви намагаєтесь виконати, не викликається) ... спробуйте і подивіться. :-)
Michael Dautermann

Як тут здати тип Bool?
virata

Зробіть його об’єктом стилю Objective C (наприклад, " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") і передайте його як один параметр, @virata.
Michael Dautermann

2
це окреме питання @Raj ... надішліть його окремо.
Michael Dautermann

34

Ви можете упакувати свої параметри в один об’єкт і скористатися допоміжним методом, щоб викликати свій початковий метод, як пропонували Майкл та інші.

Інший варіант - dispatch_after, який візьме блок і поставить його в чергу в певний час.

double delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

    [self fooFirstInput:first secondInput:second];

});

Або, як ви вже виявили, якщо вам не потрібна затримка, ви можете просто використати - performSelector:withObject:withObject:


Що також хорошого в цьому підході, так це те, що ви можете використовувати __weakсвій таймер удавання лише слабке посилання назад до себе - таким чином, ви не закінчите штучно продовжувати життєвий цикл свого об'єкта і, наприклад, якщо ваш performSelector: afterDelay: впливає щось на зразок хвоста рекурсія (хоча і без рекурсії), тоді вона вирішує цикл збереження.
Томмі

1
Так, це має бути прийнятою відповіддю. Це більш доречно і прямо вперед.
Roohul

7

Найпростіший варіант - змінити метод, щоб взяти один параметр, що містить обидва аргументи, наприклад, NSArrayабо NSDictionary(або додати другий метод, який приймає один параметр, розпаковує його та викликає перший метод, а потім викликає другий метод на затримка).

Наприклад, ви можете мати щось на зразок:

- (void) fooOneInput:(NSDictionary*) params {
    NSString* param1 = [params objectForKey:@"firstParam"];
    NSString* param2 = [params objectForKey:@"secondParam"];
    [self fooFirstInput:param1 secondInput:param2];
}

А потім, щоб зателефонувати йому, ви можете зробити:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];

Що робити, якщо метод неможливо змінити, скажімо, він живе в UIKit чи щось інше? Мало того, зміна методу використання також NSDictionaryвтрачає безпеку типу. Не ідеально.
fatuhoku

@fatuhoku - це охоплюється дужками; "додати другий метод, який приймає один параметр, розпаковує його і викликає перший метод". Це працює незалежно від того, де живе перший метод. Що стосується безпеки типу, це було втрачено в момент прийняття рішення про використання performSelector:(або NSInvocation). Якщо це викликає занепокоєння, найкращим варіантом було б пройти GCD.
aroth

6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

і назвіть це за допомогою:

[self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first", @"second", nil] afterDelay:15.0];

5

Ви можете знайти всі типи наданих методів perforSelector: тут:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html

Існує купа варіацій, але немає версії, яка приймає кілька об’єктів, а також затримку. Натомість вам потрібно буде обгортати свої аргументи в NSArray або NSDictionary.

- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
 performSelector:withObject:afterDelay:
 performSelector:withObject:afterDelay:inModes:
 performSelectorOnMainThread:withObject:waitUntilDone:
 performSelectorOnMainThread:withObject:waitUntilDone:modes:
 performSelector:onThread:withObject:waitUntilDone:
 performSelector:onThread:withObject:waitUntilDone:modes:
 performSelectorInBackground:withObject: 

2

Мені не подобається спосіб NSInvocation, занадто складний. Будьмо простими та чистими:

// Assume we have these variables
id target, SEL aSelector, id parameter1, id parameter2;

// Get the method IMP, method is a function pointer here.
id (*method)(id, SEL, id, id) = (void *)[target methodForSelector:aSelector];

// IMP is just a C function, so we can call it directly.
id returnValue = method(target, aSelector, parameter1, parameter2);

Приємно! Замініть "vc" на "target"
Антон,

1

Я щойно зробив щось, і мені потрібно було зателефонувати оригінальному методу. Що я зробив, це склав протокол і кинув на нього свій об’єкт. Інший спосіб полягає у визначенні методу в категорії, але потребує придушення попередження (#pragma clang diagnostics ігнорує "-Wincomplete-реализации").


0

Простий і багаторазовий спосіб - це розширення NSObjectта впровадження

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;

щось на зразок:

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
    NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
    [invocation setSelector: aSelector];

    int index = 2; //0 and 1 reserved
    for (NSObject *argument in arguments) {
        [invocation setArgument: &argument atIndex: index];
        index ++;
    }
    [invocation invokeWithTarget: self];
}

0

Я б просто створив власний об'єкт, що містить усі мої параметри як властивості, а потім використовував би цей єдиний об'єкт як параметр

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