NSObject
的performSelector:withObject:afterDelay:
方法允许我在一定时间后调用带有对象参数的对象的方法。它不能用于带有非对象参数(例如int、float、struct、非对象指针等)的方法。
如何用一个带有非对象参数的方法实现同样的功能?我知道对于普通的performSelector:withObject:
,解决方案是使用NSInvocation
(顺便说一下,这真的很复杂)。但我不知道如何处理“延迟”部分。
谢谢,
NSObject
的performSelector:withObject:afterDelay:
方法允许我在一定时间后调用带有对象参数的对象的方法。它不能用于带有非对象参数(例如int、float、struct、非对象指针等)的方法。
如何用一个带有非对象参数的方法实现同样的功能?我知道对于普通的performSelector:withObject:
,解决方案是使用NSInvocation
(顺便说一下,这真的很复杂)。但我不知道如何处理“延迟”部分。
谢谢,
这是我用来调用无法使用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
消息吗? - Peter Hosey[anInvocation performSelector:@selector(invoke) afterDelay:0.5];
- Morty只需将浮点数、布尔值、整型或类似类型包装在NSNumber中。
对于结构体,我不知道有什么便捷的解决方案,但您可以创建一个独立的ObjC类来拥有这样的结构体。
BOOL
参数传递nil
以接收NO
(FALSE
)。 - Nicolas Miari如果参数是BOOL类型,有一个简单的技巧。
传递nil表示NO,传递self表示YES。nil被转换为NO的BOOL值。self被转换为YES的BOOL值。
如果参数不是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];
self
地址为0x0123400
时会发生什么。使用这种方法,你将在0.4%的情况下得到NO
而不是YES
。更糟糕的是,有了这个概率,这种解决方案可能会通过所有测试,并在后来揭示问题。 - Oleg Trakhman我知道这是一个老问题,但如果你正在构建 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];
});
NSValue
“解包”为预期的类型
? - Alex GrayPerformSelector: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] ];
}
块是前进的方式。您可以具有复杂参数、类型安全性,而且它比大多数旧答案都要简单和更安全。例如,您可以只写如下代码:
[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)作为另一个例子。
我建议您使用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]];
}
}
我也想用一种接收BOOL参数的方法来实现这个,但是用NSNumber包装bool值后,没能成功传递该值。我不知道为什么会出现这种情况。
所以我最终采取了一个简单的hack方法。我将所需的参数放在另一个虚拟函数中,并使用performSelector调用该函数,其中withObject = nil;
[self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0];
-(void)dummyCaller {
[self myFunction:YES];
}
-performSelector[...]
方法期望一个对象(而不是原始数据类型),并且它不知道所调用的选择器接受布尔值,也许NSNumber
实例的地址(指针值)被盲目地转换为BOOL
(非零,即TRUE
)。也许运行时可以比这更聪明,并识别包装在NSNumber
中的零布尔值! - Nicolas Miari我发现最快(但有点不规范)的方法是直接调用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在底层使用的相同机制发送消息。