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

时间:2022-01-03 13:40:11

The NSObject method performSelector:withObject:afterDelay: allows me to invoke a method on the object with an object argument after a certain time. It cannot be used for methods with a non-object argument (e.g. ints, floats, structs, non-object pointers, etc.).

NSObject方法performSelector:withObject:afterDelay:允许我在某个时间后用对象参数调用对象上的方法。它不能用于非对象参数的方法(例如:ints、float、struct、非对象指针等)。

What is the simplest way to achieve the same thing with a method with a non-object argument? I know that for regular performSelector:withObject:, the solution is to use NSInvocation (which by the way is really complicated). But I don't know how to handle the "delay" part.

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

Thanks,

谢谢,

13 个解决方案

#1


69  

Here is what I used to call something I couldn't change using NSInvocation:

这是我过去常说的,我不能改变使用nsin假期:

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


35  

Just wrap the float, boolean, int or similar in an NSNumber.

只需将浮点数、布尔值、整数或类似的NSNumber包装起来。

For structs, I don't know of a handy solution, but you could make a separate ObjC class that owns such a struct.

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

#3


13  

DO NOT USE THIS ANSWER. I HAVE ONLY LEFT IT FOR HISTORICAL PURPOSES. SEE THE COMMENTS BELOW.

不要使用这个答案。我只是为了历史目的才离开的。请参见下面的评论。

There is a simple trick if it is a BOOL parameter.

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

Pass nil for NO and self for YES. nil is cast to the BOOL value of NO. self is cast to the BOOL value of YES.

为“不”和“我”通过nil。nil被投到NO的BOOL值中。自我被投到“是的”的BOOL值中。

This approach breaks down if it is anything other than a BOOL parameter.

如果不是BOOL参数,这种方法就会崩溃。

Assuming self is a UIView.

假设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


8  

Perhaps NSValue, just make sure your pointers are still valid after the delay (ie. no objects allocated on stack).

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

#5


7  

I know this is an old question but if you are building iOS SDK 4+ then you can use blocks to do this with very little effort and make it more readable:

我知道这是一个老问题,但是如果你正在构建iOS SDK 4+,那么你可以用block来做这一点,并且让它更容易阅读:

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


5  

Blocks are the way to go. You can have complex parameters, type safety, and it's a lot simpler and safer than most of the old answers here. For example, you could just write:

街区就是我们要走的路。你可以有复杂的参数,类型安全,它比这里的大多数旧的答案要简单,安全得多。例如,你可以这样写:

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

Blocks allow you to capture arbitrary parameter lists, reference objects and variables.

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

Backing Implementation (basic):

支持实现(基本):

@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

Example:

例子:

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

Also see Michael's answer (+1) for another example.

另一个例子是Michael的答案(+1)。

#7


4  

PerformSelector:WithObject always takes an object, so in order to pass arguments like int/double/float etc..... You can use something like this.

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

Same way you can use [NSNumber numberWithInt:] etc.... and in the receiving method you can convert the number into your format as [number int] or [number double].

同样可以使用[NSNumber numberWithInt:]等....在接收方法中,您可以将数字转换为[数字int]或[number double]。

#8


3  

I would always recomend that you use NSMutableArray as the object to pass on. This is because you can then pass several objects, like the button pressed and other values. NSNumber, NSInteger and NSString are just containers of some value. Make sure that when you get the object from the array that you refer to to a correct container type. You need to pass on NS containers. There you may test the value. Remember that containers use isEqual when values are compared.

我总是会把你使用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


2  

I also wanted to do this, but with a method that receives a BOOL parameter. Wrapping the bool value with NSNumber, FAILED TO PASS THE VALUE. I have no idea why.

我也想做这个,但是用一个接收BOOL参数的方法。用NSNumber包装bool值,未能传递该值。我不知道为什么。

So I ended up doing a simple hack. I put the required parameter in another dummy function and call that function using the performSelector, where withObject = nil;

最后我做了一个简单的hack。我将所需的参数放到另一个虚拟函数中,并使用performSelector调用该函数,其中withObject = nil;

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

-(void)dummyCaller {

[self myFunction:YES];

}

#10


2  

I find that the quickest (but somewhat dirty) way to do this is by invoking objc_msgSend directly. However, it's dangerous to invoke it directly because you need to read the documentation and make sure that you're using the correct variant for the type of return value and because objc_msgSend is defined as vararg for compiler convenience but is actually implemented as fast assembly glue. Here's some code used to call a delegate method -[delegate integerDidChange:] that takes a single integer argument.

我发现直接调用objc_msgSend是实现这一目标的最快方法(但有点脏)。但是,直接调用它是危险的,因为您需要阅读文档,并确保您使用的是返回值类型的正确变量,因为objc_msgSend被定义为为编译器方便而使用的vararg,但实际上是作为快速组装glue实现的。这里有一些用于调用委托方法的代码——[委托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);
}

This first saves the selector since we're going to refer to it multiple times and it would be easy to create a typo. It then verifies that the delegate actually responds to the selector - it might be an optional protocol. It then creates a function pointer type that specifies the actual signature of the selector. Keep in mind that all Objective-C messages have two hidden first arguments, the object being messaged and the selector being sent. Then we create a function pointer of the appropriate type and set it to point to the underlying objc_msgSend function. Keep in mind that if the return value is a float or struct, you need to use a different variant of objc_msgSend. Finally, send the message using the same machinery that Objective-C uses under the sheets.

这首先保存了选择器,因为我们将多次引用它,并且很容易创建一个typo。然后,它验证委托是否实际响应选择器——它可能是一个可选的协议。然后,它创建一个函数指针类型,指定选择器的实际签名。请记住,所有的Objective-C消息都有两个隐藏的第一个参数,对象是消息传递的,而选择器是被发送的。然后我们创建一个适当类型的函数指针,并将其设置为指向基础objc_msgSend函数。请记住,如果返回值是float或struct,则需要使用objc_msgSend的不同变体。最后,使用Objective-C使用的相同机制发送消息。

#11


1  

You Could just use NSTimer to call a selector:

你可以用NSTimer来调用选择器:

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

#12


1  

Calling performSelector with an NSNumber or other NSValue will not work. Instead of using the value of the NSValue/NSNumber, it will effectively cast the pointer to an int, float, or whatever and use that.

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

But the solution is simple and obvious. Create the NSInvocation and call

但解决方法简单明了。创建nsin使命和调用。

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

(调用performSelector:@ selector(调用)withObject:nil afterDelay:延迟)

#13


0  

Pehaps...ok, very likely, I'm missing something, but why not just create an object type, say NSNumber, as a container to your non-object type variable, such as CGFloat?

也许……很可能,我遗漏了一些东西,但是为什么不创建一个对象类型,比如NSNumber,作为非对象类型变量的容器,比如CGFloat?

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

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

#1


69  

Here is what I used to call something I couldn't change using NSInvocation:

这是我过去常说的,我不能改变使用nsin假期:

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


35  

Just wrap the float, boolean, int or similar in an NSNumber.

只需将浮点数、布尔值、整数或类似的NSNumber包装起来。

For structs, I don't know of a handy solution, but you could make a separate ObjC class that owns such a struct.

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

#3


13  

DO NOT USE THIS ANSWER. I HAVE ONLY LEFT IT FOR HISTORICAL PURPOSES. SEE THE COMMENTS BELOW.

不要使用这个答案。我只是为了历史目的才离开的。请参见下面的评论。

There is a simple trick if it is a BOOL parameter.

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

Pass nil for NO and self for YES. nil is cast to the BOOL value of NO. self is cast to the BOOL value of YES.

为“不”和“我”通过nil。nil被投到NO的BOOL值中。自我被投到“是的”的BOOL值中。

This approach breaks down if it is anything other than a BOOL parameter.

如果不是BOOL参数,这种方法就会崩溃。

Assuming self is a UIView.

假设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


8  

Perhaps NSValue, just make sure your pointers are still valid after the delay (ie. no objects allocated on stack).

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

#5


7  

I know this is an old question but if you are building iOS SDK 4+ then you can use blocks to do this with very little effort and make it more readable:

我知道这是一个老问题,但是如果你正在构建iOS SDK 4+,那么你可以用block来做这一点,并且让它更容易阅读:

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


5  

Blocks are the way to go. You can have complex parameters, type safety, and it's a lot simpler and safer than most of the old answers here. For example, you could just write:

街区就是我们要走的路。你可以有复杂的参数,类型安全,它比这里的大多数旧的答案要简单,安全得多。例如,你可以这样写:

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

Blocks allow you to capture arbitrary parameter lists, reference objects and variables.

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

Backing Implementation (basic):

支持实现(基本):

@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

Example:

例子:

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

Also see Michael's answer (+1) for another example.

另一个例子是Michael的答案(+1)。

#7


4  

PerformSelector:WithObject always takes an object, so in order to pass arguments like int/double/float etc..... You can use something like this.

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

Same way you can use [NSNumber numberWithInt:] etc.... and in the receiving method you can convert the number into your format as [number int] or [number double].

同样可以使用[NSNumber numberWithInt:]等....在接收方法中,您可以将数字转换为[数字int]或[number double]。

#8


3  

I would always recomend that you use NSMutableArray as the object to pass on. This is because you can then pass several objects, like the button pressed and other values. NSNumber, NSInteger and NSString are just containers of some value. Make sure that when you get the object from the array that you refer to to a correct container type. You need to pass on NS containers. There you may test the value. Remember that containers use isEqual when values are compared.

我总是会把你使用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


2  

I also wanted to do this, but with a method that receives a BOOL parameter. Wrapping the bool value with NSNumber, FAILED TO PASS THE VALUE. I have no idea why.

我也想做这个,但是用一个接收BOOL参数的方法。用NSNumber包装bool值,未能传递该值。我不知道为什么。

So I ended up doing a simple hack. I put the required parameter in another dummy function and call that function using the performSelector, where withObject = nil;

最后我做了一个简单的hack。我将所需的参数放到另一个虚拟函数中,并使用performSelector调用该函数,其中withObject = nil;

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

-(void)dummyCaller {

[self myFunction:YES];

}

#10


2  

I find that the quickest (but somewhat dirty) way to do this is by invoking objc_msgSend directly. However, it's dangerous to invoke it directly because you need to read the documentation and make sure that you're using the correct variant for the type of return value and because objc_msgSend is defined as vararg for compiler convenience but is actually implemented as fast assembly glue. Here's some code used to call a delegate method -[delegate integerDidChange:] that takes a single integer argument.

我发现直接调用objc_msgSend是实现这一目标的最快方法(但有点脏)。但是,直接调用它是危险的,因为您需要阅读文档,并确保您使用的是返回值类型的正确变量,因为objc_msgSend被定义为为编译器方便而使用的vararg,但实际上是作为快速组装glue实现的。这里有一些用于调用委托方法的代码——[委托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);
}

This first saves the selector since we're going to refer to it multiple times and it would be easy to create a typo. It then verifies that the delegate actually responds to the selector - it might be an optional protocol. It then creates a function pointer type that specifies the actual signature of the selector. Keep in mind that all Objective-C messages have two hidden first arguments, the object being messaged and the selector being sent. Then we create a function pointer of the appropriate type and set it to point to the underlying objc_msgSend function. Keep in mind that if the return value is a float or struct, you need to use a different variant of objc_msgSend. Finally, send the message using the same machinery that Objective-C uses under the sheets.

这首先保存了选择器,因为我们将多次引用它,并且很容易创建一个typo。然后,它验证委托是否实际响应选择器——它可能是一个可选的协议。然后,它创建一个函数指针类型,指定选择器的实际签名。请记住,所有的Objective-C消息都有两个隐藏的第一个参数,对象是消息传递的,而选择器是被发送的。然后我们创建一个适当类型的函数指针,并将其设置为指向基础objc_msgSend函数。请记住,如果返回值是float或struct,则需要使用objc_msgSend的不同变体。最后,使用Objective-C使用的相同机制发送消息。

#11


1  

You Could just use NSTimer to call a selector:

你可以用NSTimer来调用选择器:

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

#12


1  

Calling performSelector with an NSNumber or other NSValue will not work. Instead of using the value of the NSValue/NSNumber, it will effectively cast the pointer to an int, float, or whatever and use that.

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

But the solution is simple and obvious. Create the NSInvocation and call

但解决方法简单明了。创建nsin使命和调用。

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

(调用performSelector:@ selector(调用)withObject:nil afterDelay:延迟)

#13


0  

Pehaps...ok, very likely, I'm missing something, but why not just create an object type, say NSNumber, as a container to your non-object type variable, such as CGFloat?

也许……很可能,我遗漏了一些东西,但是为什么不创建一个对象类型,比如NSNumber,作为非对象类型变量的容器,比如CGFloat?

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

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