Как использовать 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];