Як використовувати performSelector: withObject: afterDelay: з примітивними типами в какао?


97

NSObjectМетод performSelector:withObject:afterDelay:дозволяє мені викликати метод об'єкта з об'єктом спору по закінченні певного часу. Він не може бути використаний для методів з не-об’єктним аргументом (наприклад, ints, floats, structs, ne-object pointers тощо).

Який найпростіший спосіб досягти тієї ж речі методом з не об’єктним аргументом? Я знаю, що для регулярного performSelector:withObject:рішення - це використовувати NSInvocation(що, до речі, справді складне). Але я не знаю, як впоратися з частиною "затримки".

Дякую,


Це трохи хакіт, але мені здається корисним писати швидкий код. Щось на зразок: id object = [array performSelector: @selector (objectAtIndex :) withObject: (__bridge_transfer) (void *) 1]; . Якщо аргумент 0, ви навіть не потребуєте мосту, оскільки 0 "спеціальний", і id сумісний з ним.
Рамі Аль Зухорі

Відповіді:


72

Ось що я називав те, що я не міг змінити за допомогою NSInvocation:

SEL theSelector = NSSelectorFromString(@"setOrientation:animated:");
NSInvocation *anInvocation = [NSInvocation
            invocationWithMethodSignature:
            [MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]];

[anInvocation setSelector:theSelector];
[anInvocation setTarget:theMovie];
UIInterfaceOrientation val = UIInterfaceOrientationPortrait;
BOOL anim = NO;
[anInvocation setArgument:&val atIndex:2];
[anInvocation setArgument:&anim atIndex:3];

[anInvocation performSelector:@selector(invoke) withObject:nil afterDelay:1];

Чому б просто не зробити [theMovie setOrientation: UIInterfaceOrientationPortrait animated:NO]? Або ви маєте на увазі, що invokeповідомлення є у способі, який ви виконуєте після відстрочки?
Пітер Хосей

Ага, я забув сказати .. `[anInvocation performSelector: @selector (invoke) afterDelay: 0.5];
Морті

24
Лише бічна примітка для тих, хто не знає, ми починаємо додавати аргументи з індексу 2, оскільки індекс 0 і 1 зарезервовані для прихованих аргументів "self" і "_cmd".
Вішал Сінгх

35

Просто загорніть поплавок, булевий, int чи подібний до NSNumber.

Для структур я не знаю зручного рішення, але ви можете зробити окремий клас ObjC, який має таку структуру.


5
Створіть метод обгортки, -delayedMethodWithArgument: (NSNumber *) arg. Нехай він викликає оригінальний метод після витягування примітиву з NSNumber.
Джеймс Вільямс

1
Зрозумів. Тоді я не впевнений у витонченому забрудненні, але ви можете додати інший метод, який призначений як ціль вашого PerformSelector :, який розпаковує NSNumber і виконує остаточний селектор за методом, який ви зараз маєте на увазі. Ще одна ідея, яку ви могли б вивчити, - це створити категорію на NSObject, яка додасть perforSelector: withInt: ... (і подібне).
шкодить

32
NSValue (суперклас NSNumber) пригорне все, що ви йому надасте. Якщо ви хочете обернути екземпляр 'struct foo', який називається 'bar', ви зробите '[NSValue valueWithBytes: & bar objCType: @encode (struct foo)];'
Джим Дові

2
Ви можете використовувати NSValue, щоб обернути структуру. У будь-якому випадку, NSNumber / NSValue - правильне рішення.
Пітер Хосей

6
Підтверджено: ОБОВ'ЯЗКОВО пройти nilдля отримання BOOLпараметра NO( FALSE)
Nicolas Miari

13

НЕ ВИКОРИСТОВУЙТЕ ЦЕ ВІДПОВІДЬ. Я ТОЛЬКО ВЛЕШУЮ ІСТОРИЧНІ ЦІЛІ. ДИВІТЬСЯ КОМЕНТАРИ ВИПУСКУ.

Існує простий трюк, якщо це параметр BOOL.

Пройдіть нуль для НІ та "Я" для ТАК. нуль приводиться до значення BOOL NO. self передається до значення BOOL ТАК.

Цей підхід руйнується, якщо це щось інше, ніж параметр BOOL.

Припустимо, що "себе" - це UIView.

//nil will be cast to NO when the selector is performed
[self performSelector:@selector(setHidden:) withObject:nil afterDelay:5.0];

//self will be cast to YES when the selector is performed
[self performSelector:@selector(setHidden:) withObject:self afterDelay:10.0];

Я вважаю, що значення BOOL завжди повинні бути 0 або 1. Це правда, що більшість кодів не хвилюватиметься, і він просто зробить if ()тест на ньому, але можливо, що деякий код залежатиме від того, щоб він був 0 або 1, і таким чином виграв не трактуйте якесь велике адресне значення як те саме, що 1 (ТАК).
користувач102008

1
Дякуємо за це, не хотіли створювати обгортку або писати некрасивий синтаксис GCD для цього.
0xSina

10
НЕ ВИКОРИСТОВУЙТЕ НІКОЛИ . Цей підхід є джерелом серйозних помилок, важко знайти. Уявіть, selfяк адреса, як 0x0123400. З цим підходом ви не будете вводити ТАК у 0,4% випадків. Гірше, з цією ймовірністю, це рішення може пройти всі тести і виявити пізніше.
Олег Трахман

1
UPD: НЕ БУДЬ введено ТАК з 6% ймовірності через вирівнювання пам'яті.
Олег Трахман

4
@iVishal Ознайомтеся з гострими кутами BOOL
Farray

8

Можливо, NSValue , просто переконайтеся, що ваші покажчики залишаються дійсними після затримки (тобто немає об’єктів, виділених у стеці).


Чи будь-який старий метод "розпакує" NSValueочікуване type?
Алекс Сірий

8

Я знаю, що це старе питання, але якщо ви будуєте iOS SDK 4+, тоді ви можете використовувати блоки, щоб зробити це з дуже невеликими зусиллями та зробити його більш читабельним:

double delayInSeconds = 2.0;
int primitiveValue = 500;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self doSomethingWithPrimitive:primitiveValue];     
});

Це створює утримуючий цикл. Для правильної роботи вам потрібно зробити слабке посилання на себе і використовувати це посилання для виконання селектора. "__block typeof (self) слабкийSelf = self;"
Тім Вахтер

1
Тут вам не потрібна слабка посилання, тому що self не зберігає блок (немає циклу збереження). Напевно, краще використовувати сильну орієнтир насправді, оскільки самоврядування може дістатись інакше. Дивіться: stackoverflow.com/a/19018633/251687
Себастьєн Мартін

6

PerformSelector: WithObject завжди бере об’єкт, тому для передачі аргументів, таких як int / double / float і т. Д. ... Ви можете використовувати щось подібне.

//NSNumber is an object..

    [self performSelector:@selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
    afterDelay:1.5];

    -(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];
    }

Таким же чином можна використовувати [NSNumber numberWithInt:] і т. Д. ...., а в способі отримання ви можете перетворити число у ваш формат у вигляді [число int] або [число подвійне].


1
Якщо обмежено значення + performSelector: withObject: +, це єдиний правильний відповідь, розміщений у IMO. Але відповідь @ MichaelGaylord з використанням блоків є більш чистою, якщо це варіант для вас.
big_m

5

Блоки - це шлях. Ви можете мати складні параметри, тип безпеки, і це набагато простіше і безпечніше, ніж більшість старих відповідей тут. Наприклад, ви можете просто написати:

[MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2];

Блоки дозволяють фіксувати довільні списки параметрів, посилання на об'єкти та змінні.

Реалізація резервної копії (основна):

@interface MONBlock : NSObject

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay;

@end

@implementation MONBlock

+ (void)imp_performBlock:(void(^)())pBlock
{
 pBlock();
}

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay
{
  [self performSelector:@selector(imp_performBlock:)
             withObject:[pBlock copy]
             afterDelay:pDelay];
}

@end

Приклад:

int main(int argc, const char * argv[])
{
 @autoreleasepool {
  __block bool didPrint = false;
  int pi = 3; // close enough =p

  [MONBlock performBlock:^{NSLog(@"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2];

  while (!didPrint) {
   [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]];
  }

  NSLog(@"(Bye, World!)");
 }
 return 0;
}

Також дивіться відповідь Майкла (+1) для іншого прикладу.


3

Я завжди рекомендую вам використовувати NSMutableArray як об'єкт для передачі. Це тому, що потім ви можете передати кілька об'єктів, наприклад, натиснуту кнопку та інші значення. NSNumber, NSInteger та NSString - лише контейнери певної цінності. Переконайтеся, що коли ви отримуєте об'єкт з масиву, на який ви посилаєтесь, на правильний тип контейнера. Вам потрібно передати NS контейнери. Там ви можете перевірити значення. Пам’ятайте, що контейнери використовують isEqual при порівнянні значень.

#define DELAY_TIME 5

-(void)changePlayerGameOnes:(UIButton*)sender{
    NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ];
    NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil];
    [array addObject:nextPlayer];
    [self performSelector:@selector(next:) withObject:array afterDelay:DELAY_TIME];
}
-(void)next:(NSMutableArray*)nextPlayer{
    if(gdata != nil){ //if game choose next player
       [self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]];
    }
}

2

Я також хотів це зробити, але з методом, який отримує параметр BOOL. Згорнувши значення bool за допомогою NSNumber, НЕ вдалося пропустити значення. Я поняття не маю, чому.

Тож я закінчив робити просту хаку. Я поклав необхідний параметр в іншу фіктивну функцію і викликаю цю функцію за допомогою PerformSelector, де withObject = nil;

[self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0];

-(void)dummyCaller {

[self myFunction:YES];

}

Дивіться мій коментар вище щодо відповіді шкоди. Виглядає так, оскільки -performSelector[...]метод очікує, що об'єкт (замість примітивного типу даних), і він не знає, що викликаний селектор приймає булеве значення, можливо, адресу (значення вказівника) NSNumberекземпляра сліпо 'приведено' до BOOL(= ненульовий, тобто TRUE). Можливо, час виконання може бути розумнішим за це і розпізнати нульовий булів, загорнутий у NSNumber!
Nicolas Miari

Я згоден. Я думаю, що краще, щоб уникнути BOOL взагалі і використовувати ціле число 0 для NO і 1 для YES, і обернути це за допомогою NSNumber або NSValue.
GeneCode

Це щось змінює? Якщо так, раптом я справді розчарований у Какао :)
Ніколас Міарі

2

Я вважаю, що найшвидший (але дещо брудний) спосіб зробити це шляхом виклику objc_msgSend безпосередньо. Однак викликати це безпосередньо небезпечно, тому що вам потрібно прочитати документацію та переконатися, що ви використовуєте правильний варіант для типу повернутого значення і тому, що objc_msgSend визначається як vararg для зручності компілятора, але насправді реалізується як клей швидкої збірки . Ось якийсь код, який використовується для виклику методу делегата - [delegate integerDidChange:], який приймає єдиний цілий аргумент.

#import <objc/message.h>


SEL theSelector = @selector(integerDidChange:);
if ([self.delegate respondsToSelector:theSelector])
{
    typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger);
    IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend;
    MyFunction(self.delegate, theSelector, theIntegerThatChanged);
}

Це вперше зберігає селектор, оскільки ми збираємося посилатися на нього кілька разів, і було б легко створити помилку друку. Потім він перевіряє, що делегат насправді відповідає селектору - це може бути необов'язковий протокол. Потім створюється тип вказівника функції, який визначає фактичну підпис селектора. Майте на увазі, що всі повідомлення Objective-C мають два прихованих перших аргументу, обмінюється об'єктом та відправляється селектором. Потім ми створюємо покажчик функції відповідного типу і встановлюємо його, щоб вказувати на основну функцію objc_msgSend. Майте на увазі, що якщо значення, що повертається, є плаваючою або структурою, вам потрібно використовувати інший варіант objc_msgSend. Нарешті, надішліть повідомлення за допомогою тих же механізмів, які використовує Objective-C під аркушами.


"Майте на увазі, що якщо ви надсилаєте floats або structs ...", ви маєте на увазі повернення
floats

Ці три рядки забезпечують безпеку типу та гарантують, що кількість аргументів - це те, що очікує ваш вибір. objc_msgSend - це функція vararg, яка реально реалізована як складальний клей, тому, якщо ви не набираєте клавішу, ви можете їй надати що завгодно.
Джейсон Харріс

1

Ви можете просто використовувати NSTimer для виклику селектора:

[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(yourMethod:) userInfo:nil repeats:NO]

Ви пропускаєте аргумент. Аргументом yourMethod:у вашому прикладі є сам таймер, і ви нічого не пройшли userInfo. Навіть якби ви все-таки використовували userInfo, вам все одно доведеться зафіксувати значення, як наводив шкоду і Ремус Русану.
Пітер Хосей

-1 за те, що не використовується PerformSelector та створюється непотрібний екземпляр
NSTimer

1

Виклик performSelector за допомогою NSNumber або іншої NSValue не буде працювати. Замість того, щоб використовувати значення NSValue / NSNumber, воно ефективно передасть покажчик на int, float або будь-що і використовуватиме це.

Але рішення просте і очевидне. Створіть NSInvocation та зателефонуйте

[invocation performSelector:@selector(invoke) withObject:nil afterDelay:delay]


0

Можливо ... добре, дуже певно, мені щось не вистачає, але чому б просто не створити тип об'єкта, скажімо, NSNumber, як контейнер до змінної вашого типу, що не є об'єктом, наприклад, CGFloat?

CGFloat myFloat = 2.0; 
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];

[self performSelector:@selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];

Питання стосується передачі примітивних типів, а не об'єктів NSNumber.
Рудольф Адамкович

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