如何使用performSelector:withObject:afterDelay:在Cocoa中使用原始类型?

时间:2009-05-24 19:46:18

标签: objective-c cocoa

NSObject方法performSelector:withObject:afterDelay:允许我在一定时间后用对象参数调用对象上的方法。它不能用于具有非对象参数的方法(例如,整数,浮点数,结构,非对象指针等)。

使用非对象参数的方法实现同样的事情的最简单方法是什么?我知道对于常规performSelector:withObject:,解决方案是使用NSInvocation(顺便说一句,这非常复杂)。但我不知道如何处理“延迟”部分。

谢谢,

13 个答案:

答案 0 :(得分:69)

以下是我过去常常使用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];

答案 1 :(得分:35)

只需在NSNumber中包装float,boolean,int或类似内容。

对于结构体,我不知道一个方便的解决方案,但你可以创建一个拥有这样一个结构的单独的ObjC类。

答案 2 :(得分:13)

请勿使用此答案。我只是为了历史目的而离开了它。请参阅下面的评论。

如果它是BOOL参数,有一个简单的技巧。

通过n为NO而self为YES。 nil被转换为NO的BOOL值。 self被转换为BOOL值为YES。

如果它不是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];

答案 3 :(得分:8)

也许NSValue,只是确保你的指针在延迟后仍然有效(即没有在堆栈上分配对象)。

答案 4 :(得分: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];     
});

答案 5 :(得分: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]。

答案 6 :(得分:5)

块是可行的方法。您可以拥有复杂的参数,类型安全,并且它比这里的大多数旧答案更简单,更安全。例如,你可以写:

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

Blocks允许您捕获任意参数列表,引用对象和变量。

支持实施(基本):

@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;
}

另见另一个例子,请参阅Michael的答案(+1)。

答案 7 :(得分: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]];
    }
}

答案 8 :(得分:2)

我也想这样做,但是使用一个接收BOOL参数的方法。用NSNumber包装bool值,未通过该值。我不明白为什么。

所以我最后做了一个简单的黑客攻击。我将所需的参数放在另一个虚函数中并使用performSelector调用该函数,其中withObject = nil;

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

-(void)dummyCaller {

[self myFunction:YES];

}

答案 9 :(得分: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函数。请记住,如果返回值是float或struct,则需要使用objc_msgSend的不同变体。最后,使用Objective-C在工作表下使用的相同机制发送消息。

答案 10 :(得分:1)

您可以使用NSTimer来调用选择器:

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

答案 11 :(得分:1)

使用NSNumber或其他NSValue调用performSelector将不起作用。它不会使用NSValue / NSNumber的值,而是将指针有效地转换为int,float或其他任何内容并使用它。

但解决方案很简单明了。创建NSInvocation并调用

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

答案 12 :(得分:0)

Pehaps ...好吧,很可能,我错过了什么,但为什么不创建一个对象类型,比如说NSNumber,作为非对象类型变量的容器,比如CGFloat?

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

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