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

Метод NSObject performSelector:withObject:afterDelay: позволяет мне вызвать метод объекта с аргументом объекта через определенное время. Он не может использоваться для методов с аргументом non-object (например, ints, floats, structs, указатели без объектов и т.д.).

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

Спасибо,

Ответ 1

Вот что я использовал для вызова чего-то, что я не мог изменить, используя 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];

Ответ 2

Просто оберните float, boolean, int или аналогичный в NSNumber.

Для структур я не знаю удобного решения, но вы можете создать отдельный класс ObjC, которому принадлежит такая структура.

Ответ 3

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

Существует простой трюк, если это параметр 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];

Ответ 4

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

Ответ 5

Я знаю, что это старый вопрос, но если вы создаете 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];     
});

Ответ 6

Блоки - это путь. У вас могут быть сложные параметры, тип безопасности, и это намного проще и безопаснее, чем большинство старых ответов здесь. Например, вы можете просто написать:

[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) для другого примера.

Ответ 7

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] или [number double].

Ответ 8

Я всегда рекомендую, чтобы вы использовали 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]];
    }
}

Ответ 9

Я также хотел это сделать, но с помощью метода, который получает параметр BOOL. Обнуление значения bool с помощью NSNumber, НЕ ДОЛЖНО ПРОХОДИТЬ ЗНАЧЕНИЕ. Я понятия не имею, почему.

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

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

-(void)dummyCaller {

[self myFunction:YES];

}

Ответ 10

Я считаю, что самый быстрый (но несколько грязный) способ сделать это - напрямую вызвать objc_msgSend. Тем не менее, это опасно вызвать его непосредственно, потому что вам нужно прочитать документацию и убедиться, что вы используете правильный вариант для типа возвращаемого значения, и поскольку objc_msgSend определяется как vararg для удобства компилятора, но фактически реализуется как быстрый монтажный клей, Здесь некоторый код, используемый для вызова метода делегата - [делегировать 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. Имейте в виду, что если возвращаемое значение является float или struct, вам нужно использовать другой вариант objc_msgSend. Наконец, отправьте сообщение, используя тот же механизм, который Objective-C использует под листами.

Ответ 11

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

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

Ответ 12

Вызов функцииSelector с NSNumber или другим NSValue не будет работать. Вместо того, чтобы использовать значение NSValue/NSNumber, он эффективно применяет указатель к int, float или любому другому и использует его.

Но решение прост и очевидно. Создайте NSInvocation и вызовите

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

Ответ 13

Pehaps... ok, очень вероятно, что я что-то упустил, но почему бы просто не создать тип объекта, скажем NSNumber, как контейнер для вашей переменной типа объекта без объекта, например CGFloat?

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

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