如何在可可中与原始类型一起使用performSelector:withObject:afterDelay:?


97

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

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

谢谢,


有点黑,但是我发现编写快速代码很有用。类似的东西:id object = [array performSelector:@selector(objectAtIndex :) withObject:(__bridge_transfer)(void *)1];。如果参数为0,则您甚至不需要桥接,因为0是“特殊”且id与之兼容。
Ramy Al Zuhouri

Answers:


72

这是我过去使用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];
莫蒂

24
对于那些不知道的人,我们只是从索引2开始添加参数,因为索引0和1保留用于隐藏的参数“ self”和“ _cmd”。
维沙尔·辛格

35

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

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


5
创建一个包装方法-delayedMethodWithArgument:(NSNumber *)arg。将原语从NSNumber中拉出后,让它调用原始方法。
詹姆斯·威廉姆斯

1
明白了 然后,我不确定是否有一个优雅的解决方案,但是您可以添加另一个方法,该方法旨在作为performSelector:的目标,该方法将NSNumber打包并在您当前考虑的方法上执行最终选择器。您可以探索的另一个想法是在NSObject上创建一个类别,该类别添加perforSelector:withInt:...(和类似名称)。
伤害到

32
NSValue(NSNumber的超类)将包装您提供的所有内容。如果要包装称为“ bar”的“ struct foo”实例,则可以执行“ [NSValue valueWithBytes:&bar objCType:@encode(struct foo)];”
Jim Dovey

2
您可以使用NSValue包装结构。无论哪种方式,NSNumber / NSValue都是正确的解决方案。
彼得·霍西

6
证实:必须通过nil一个BOOL参数来接收NOFALSE
尼古拉斯Miari

13

请勿使用此答案。我仅出于历史目的保留它。请参阅下面的评论。

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

传递nil表示否,传递self表示是。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];

我认为BOOL值应始终为0或1。确实,大多数代码都不会在乎,只会对其进行if ()测试,但是可以想象,某些代码将依赖于它为0或1,因此可以获胜。不要将某个较大的地址值视为与1相同(是)。
2011年

1
为此,您不想创建包装器或编写难看的GCD语法来做到这一点。
0xSina 2012年

10
请勿使用任何人。这种方法是严重错误的根源,很难找到。想象一下self地址为0x0123400。有了这个方法,您将在0.4%的情况下获得YES。更糟糕的是,这种解决方案可能会通过所有测试,并在以后揭晓。
奥列格·特拉赫曼

1
UPD: 由于内存对齐,因此不会有6%的概率插入YES
奥列格·特拉克曼

4
@iVishal查看BOOL的尖角
Farray13年


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

这将创建一个保留循环。为了使其正常工作,您需要对self进行弱引用,并使用该引用来执行选择器。“ __block typeof(self)weakSelf = self;”
Tim Wachter

1
您在这里不需要弱引用,因为self不会保留该块(没有保留循环)。实际上,最好使用强引用,因为否则self可能会被释放。请参阅:stackoverflow.com/a/19018633/251687
塞巴斯蒂安·马丁

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]格式。


1
如果将其限制为+ performSelector:withObject:+,则这是IMO发布的唯一正确答案。但是,如果您愿意的话,@ MichaelGaylord使用块的答案会更干净。
big_m 2013年

5

障碍是要走的路。您可以使用复杂的参数,输入安全性,并且比这里的大多数旧答案更简单,更安全。例如,您可以编写:

[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)。


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

2

我也想这样做,但是要使用一种接收BOOL参数的方法。用NSNumber包装布尔值,则无法通过该值。我不知道为什么。

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

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

-(void)dummyCaller {

[self myFunction:YES];

}

请参阅我上面关于危害答案的评论。看起来像是因为该-performSelector[...]方法需要一个对象(而不是原始数据类型),并且它不知道被调用的选择器接受布尔值,所以NSNumber实例的地址(指针值)可能被盲目地“投射”到BOOL(=非零,即TRUE)。也许运行时可能比这更聪明,并且可以识别包装在NSNumber!中的零布尔值!
Nicolas Miari 2012年

我同意。我想最好的办法是完全避免使用BOOL,将整数0用作NO,将1用作YES,然后将其与NSNumber或NSValue进行包装。
GeneCode 2012年

这会改变什么吗?如果是这样,我突然对可可

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函数。请记住,如果返回值是浮点型或结构型,则需要使用objc_msgSend的其他变体。最后,使用工作表下的Objective-C使用的相同机器发送消息。


“请记住,如果要发送浮点数或结构...”是指返回浮点数或结构
user102008

这三行为您提供类型安全性,并确保参数数量与选择器所期望的一样。objc_msgSend是一个vararg函数,实际上已实现为程序集粘合,因此,如果您不进行类型转换,则可以提供任何内容。
杰森·哈里斯

1

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

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

你错过了争论。yourMethod:在您的示例中,to的参数是计时器本身,并且您没有为传递任何东西userInfo。即使您确实使用过userInfo,也仍然必须按照危害和Remus Rusanu的建议包装价值。
Peter Hosey 2010年

-1(不使用performSelector并创建不必要的NSTimer实例)
Applicasa iOS开发人员2012年

1

使用NSNumber或其他NSValue调用performSelector将不起作用。代替使用NSValue / NSNumber的值,它将有效地将指针强制转换为int,float或其他任何值并使用该值。

但是解决方案很简单而且很明显。创建NSInvocation并调用

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


0

可能...好吧,很可能我遗漏了一些东西,但是为什么不创建一个对象类型(例如NSNumber)作为非对象类型变量(例如CGFloat)的容器呢?

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

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

问题是关于传递原始类型,而不是NSNumber对象。
RudolfAdamkovič2012年

该问题假定OP仅对该方法具有外部访问权,而不能更改签名。
亚历克斯·格雷
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.