【问题标题】:How to use performSelector:withObject:afterDelay: with primitive types in Cocoa?如何在 Cocoa 中将 performSelector:withObject:afterDelay: 与原始类型一起使用?
【发布时间】:2010-10-28 14:50:34
【问题描述】:

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

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

谢谢,

【问题讨论】:

  • 这有点骇人听闻,但我发现编写快速代码很有用。类似于: id object= [array performSelector: @selector(objectAtIndex:) withObject: (__bridge_transfer)(void*)1]; 。如果参数是 0,你甚至不需要桥接,因为 0 是“特殊的”并且 id 与之兼容。

标签: objective-c cocoa


【解决方案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];

【讨论】:

  • 为什么不直接做[theMovie setOrientation: UIInterfaceOrientationPortrait animated:NO]?还是您的意思是invoke 消息在您执行延迟后的方法中?
  • 啊,我忘了说..`[anInvocation performSelector:@selector(invoke) afterDelay:0.5];
  • 对于那些不知道的人来说只是一个旁注,我们从索引 2 开始添加参数,因为索引 0 和 1 是为隐藏参数“self”和“_cmd”保留的。
【解决方案2】:

只需将 float、boolean、int 或类似的值包装在 NSNumber 中。

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

【讨论】:

  • 创建一个包装方法,-delayedMethodWithArgument:(NSNumber*)arg。将原语从 NSNumber 中拉出后,让它调用原始方法。
  • 明白。然后我不确定一个优雅的解决方案,但是您可以添加另一个方法,该方法旨在作为您的 performSelector: 的目标:它解压缩 NSNumber 并在您当前想到的方法上执行最终选择器。您可以探索的另一个想法是在 NSObject 上创建一个类别,添加 perforSelector:withInt:... (和类似的)。
  • NSValue(NSNumber 的超类)将包装你给它的任何东西。如果你想包装一个名为 'bar' 的 'struct foo' 实例,你可以使用 '[NSValue valueWithBytes: &bar objCType: @encode(struct foo)];'
  • 你可以使用 NSValue 来包装一个结构。无论哪种方式,NSNumber/NSValue 都是正确的解决方案。
  • 已确认:必须传递nil 以获得BOOL 参数以接收NO (FALSE)
【解决方案3】:

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

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

【讨论】:

  • 我认为 BOOL 值应该始终为 0 或 1。确实,大多数代码不会在意,只会对其进行 if () 测试,但可以想象某些代码会取决于它是 0 还是 1,因此不会将某些大地址值视为与 1 相同(是)。
  • 谢谢你,不想创建包装器或编写丑陋的 GCD 语法来做到这一点。
  • 任何人都不要使用它。这种方法是严重错误的根源,很难找到。想象self 的地址类似于0x0123400。使用这种方法,您将在 0.4% 的情况下得到NO insted YES。更糟糕的是,以这种可能性,这个解决方案可能会通过所有测试并在稍后显示。
  • UPD:由于内存对齐,一个人会以 6% 的概率得到“否”
  • @iVishal 查看BOOL’s sharp corners
【解决方案4】:

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

【讨论】:

  • 是否有任何旧方法将NSValue(如果可能)“拆箱”到预期的type
【解决方案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];     
});

【讨论】:

  • 这会创建一个保留循环。为了使其正常工作,您需要对 self 进行弱引用并使用该引用来执行选择器。 "__block typeof(self) weakSelf = self;"
  • 这里不需要弱引用,因为 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]。

【讨论】:

  • 如果仅限于 +performSelector:withObject:+,那么这是发布的唯一正确答案,IMO。但是@MichaelGaylord 使用块的答案更简洁,如果您愿意的话。
【解决方案7】:

块是要走的路。你可以有复杂的参数,类型安全,它比这里的大多数旧答案更简单、更安全。例如,你可以写:

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

另请参阅 Michael 的回答 (+1) 以了解另一个示例。

【讨论】:

    【解决方案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 参数的方法。用 NSNumber 包装布尔值,无法通过值。我不知道为什么。

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

      [self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0];
      
      -(void)dummyCaller {
      
      [self myFunction:YES];
      
      }
      

      【讨论】:

      • 查看我上面关于伤害答案的评论。看起来,因为 -performSelector[...] 方法需要一个 object (而不是原始数据类型),并且它不知道被调用的选择器接受一个布尔值,也许是地址(指针值) NSNumber 实例被盲目地“强制转换”为 BOOL (= 非零,即 TRUE )。也许运行时可能比这更智能,并且可以识别包装在 NSNumber! 中的零布尔值!
      • 我同意。我想更好的做法是完全避免使用 BOOL 并使用整数 0 表示 NO 使用整数 1 表示 YES,并用 NSNumber 或 NSValue 包装它。
      • 这有什么改变吗?如果是这样,突然间我对 Cocoa 真的很失望 :)
      【解决方案10】:

      我发现最快(但有点脏)的方法是直接调用 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 在表单下使用的相同机制发送消息。

      【讨论】:

      • “请记住,如果您要发送浮点数或结构体......”您的意思是 返回 浮点数或结构体
      • 这三行给你类型安全,并保证参数的数量是你的选择器所期望的。 objc_msgSend 是一个可变参数函数,它实际上是作为汇编胶水实现的,所以如果你不进行类型转换,你可以给它任何东西。
      【解决方案11】:

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

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

      【讨论】:

      • 你错过了这个论点。在您的示例中,yourMethod: 的参数是计时器本身,而您没有为 userInfo 传递任何内容。即使您确实使用了userInfo,您仍然需要按照harms 和Remus Rusanu 的建议将值装箱。
      • -1 不使用 performSelector 并创建不必要的 NSTimer 实例
      【解决方案12】:

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

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

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

      【讨论】:

        【解决方案13】:

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

        CGFloat myFloat = 2.0; 
        NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];
        
        [self performSelector:@selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];
        

        【讨论】:

        • 问题是关于传递原始类型,而不是 NSNumber 对象。
        • 问题假设OP只能从外部访问该方法并且不能更改签名。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-03
        • 2011-02-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多