Как использовать performSelector: withObject: afterDelay: с примитивными типами в Какао?

97

NSObjectМетод performSelector:withObject:afterDelay:позволяет мне вызвать метод объекта с объектом спора по истечении определенного времени. Его нельзя использовать для методов с аргументом, не являющимся объектом (например, целые числа, числа с плавающей запятой, структуры, указатели, не являющиеся объектами, и т. Д.).

Каков самый простой способ добиться того же с помощью метода с аргументом, не являющимся объектом? Я знаю, что для обычных performSelector:withObject:пользователей решение - использовать NSInvocation(что, кстати, действительно сложно). Но я не знаю, как справиться с «задержкой».

Спасибо,

user102008
источник
Это немного взломано, но я считаю полезным писать быстрый код. Что-то вроде: id object = [array performSelector: @selector (objectAtIndex :) withObject: (__bridge_transfer) (void *) 1]; . Если аргумент равен 0, вам даже не нужно преобразование моста, потому что 0 является «специальным» и id совместим с ним.
Ramy Al Zuhouri

Ответы:

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

Просто заверните float, boolean, 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)
Николя Миари
13

НЕ ИСПОЛЬЗУЙТЕ ЭТОТ ОТВЕТ. Я ОСТАВЛЯЛ ЕГО ТОЛЬКО ДЛЯ ИСТОРИЧЕСКИХ ЦЕЛЕЙ. СМОТРЕТЬ КОММЕНТАРИИ НИЖЕ.

Если это параметр типа BOOL, есть простой трюк.

Передайте nil вместо NO и self для YES. nil приводится к значению BOOL NO. self приводится к значению BOOL YES.

Этот подход не работает, если это какой-либо параметр, кроме BOOL.

Предполагая, что self - это 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 (ДА).
user102008
1
Спасибо за это, не хотел создавать оболочку или писать уродливый синтаксис GCD для этого.
0xSina
10
НЕ ИСПОЛЬЗУЙТЕ НИКОМУ . Такой подход является источником серьезных ошибок, которые трудно найти. Представьте себе selfс адресом вроде 0x0123400. При таком подходе вы НЕ получите ДА в 0,4% случаев. Хуже того, с этой вероятностью это решение может пройти все тесты и раскрыться позже.
Олег Трахман
1
UPD: НЕТ ДА с вероятностью 6% из-за выравнивания памяти.
Олег Трахман
4
@iVishal Оцените острые углы BOOL
Фаррей
8

Возможно, NSValue , просто убедитесь, что ваши указатели все еще действительны после задержки (т. Е. Нет объектов, размещенных в стеке).

Ремус Русану
источник
Будет ли какой-нибудь старый метод «распаковать» NSValue(если возможно) ожидаемый type?
Alex Grey
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) weakSelf = self;"
Тим Вахтер
1
Здесь вам не нужна слабая ссылка, потому что self не сохраняет блок (нет цикла сохранения). На самом деле, вероятно, предпочтительнее использовать сильную ссылку, поскольку в противном случае 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:] и т. Д. И в методе получения вы можете преобразовать число в свой формат как [number int] или [number double].

Августин
источник
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]];
    }
}
Odd-Arne
источник
2

Я тоже хотел это сделать, но с помощью метода, который получает параметр типа BOOL. Обертывание значения типа bool с помощью NSNumber, НЕ удалось передать значение. Понятия не имею почему.

В итоге я сделал простой хак. Я помещаю требуемый параметр в другую фиктивную функцию и вызываю эту функцию с помощью performSelector, где withObject = nil;

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

-(void)dummyCaller {

[self myFunction:YES];

}
GeneCode
источник
См. Мой комментарий выше к ответу вреда. Похоже, поскольку -performSelector[...]метод ожидает объект (вместо примитивного типа данных) и не знает, что вызываемый селектор принимает логическое значение, возможно, адрес (значение указателя) NSNumberэкземпляра слепо 'приведен' к BOOL(= ненулевое, т.е. TRUE). Возможно, среда выполнения могла бы быть умнее этого и распознавать нулевое логическое значение, заключенное в NSNumber!
Николас Миари
Я согласен. Я думаю, что лучше вообще избежать 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 использует под листами.

Джейсон Харрис
источник
«Имейте в виду, что если вы отправляете числа с плавающей запятой или структуры ...», вы имеете в виду возвращение
объектов с
Эти три строки обеспечивают безопасность типов и гарантируют, что количество аргументов соответствует ожиданиям вашего селектора. objc_msgSend - это функция vararg, которая фактически реализована как клей для сборки, поэтому, если вы не выполняете приведение типов, вы можете дать ей что угодно.
Джейсон Харрис,
1

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

[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(yourMethod:) userInfo:nil repeats:NO]
Unis
источник
Вы упускаете аргумент. Аргументом для 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.
Рудольф Адамкович
Вопрос предполагает, что OP имеет только внешний доступ к методу и не может изменить подпись.
Alex Grey