使用块代替performSelector:withObject:afterDelay:

88

我经常想在未来的几微秒内执行一些代码。目前,我是这样解决的:

- (void)someMethod
{
    // some code
}

而这个:

[self performSelector:@selector(someMethod) withObject:nil afterDelay:0.1];

它可以工作,但我必须每次创建一个新方法。是否可以使用块代替这个?基本上,我正在寻找像这样的方法:

[self performBlock:^{    // some code} afterDelay:0.1];

这对我来说非常有用。


2
这是一个月后的内容:https://dev59.com/8m855IYBdhLWcg3wy3oc - Jacksonkr
6个回答

107

没有内置的方法来做到这一点,但通过添加类别不太难:

@implementation NSObject (PerformBlockAfterDelay)

- (void)performBlock:(void (^)(void))block 
          afterDelay:(NSTimeInterval)delay 
{
    block = [[block copy] autorelease];
    [self performSelector:@selector(fireBlockAfterDelay:) 
               withObject:block 
               afterDelay:delay];
}

- (void)fireBlockAfterDelay:(void (^)(void))block {
    block();
}

@end

感谢Mike Ash提供了基本实现。


5
注意:你应该始终为类方法选择一个前缀,例如 mon_performBlock:afterDelay:。这将减少你的方法与其他实现发生冲突的可能性。最常见的例子是:苹果决定添加此方法——糟糕,你的方法将不会替换已经加载的方法,即使它这样做了……也会更加痛苦。 - justin
8
我应该指出 [self performBlock:^{/* some block */} afterDelay:0.1] 的想法并不是很有意义。为什么要将其附加到对象上呢?self在触发块时扮演了什么角色?最好编写一个 C 函数 RunBlockAfterDelay(void (^block)(void), NSTimeInterval delay),尽管这需要创建一个临时对象,其唯一工作是实现 -fireBlockAfterDelay: - Lily Ballard
19
仅为方便起见。如果你想以正确的方式执行此操作,则直接使用GDC并调用dispatch_after(dispatch_time_t, dispatch_queue_t, dispatch_block_t)函数是正确的方法。 - PeyloW
4
苹果的工程团队不允许在更新中破坏关键应用程序。 你真的认为OSX可以随附每个副本的Photoshop都在启动时崩溃吗? - Catfish_Man
6
我完全同意Kevin的评论:在NSObject上添加一个类别(category),它基本上用一个调用方法来替换了对dispatch_after的调用,该方法至少在Lion上的iOS模拟器中 似乎 通过调用 dispatch_after 来实现什么也不做,仅调用一个无参数块的方法。在我看来,这样就有点过度引入和包装了。特别是自从Xcode 4以来,即使没有这个类别也可以使用纯粹的dispatch_after代码片段... - danyowdee
显示剩余10条评论

41

这里是一个基于GCD的简单技术,我正在使用:

void RunBlockAfterDelay(NSTimeInterval delay, void (^block)(void))
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*delay),
      dispatch_get_current_queue(), block);
}

我不是GCD专家,而且我很想听听对这个解决方案的评论。


1
这将实现你想要的功能,但我鼓励你继续明确指定目标队列;这不需要太多额外的代码,并且可以清楚地表明执行上下文中的事物将在哪里运行。使用dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC * delay)的宏可能是减少样板文件的更有效方法。 - Catfish_Man
3
我唯一要补充的是,如果将参数顺序颠倒,调用会更加直观,因为它们的顺序将遵循函数名称中名词的顺序。 - danyowdee
3
如其他回答所述,@danyowdee 的说法是错误的。根据http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html,“块应始终是方法的最后一个参数”。 - SilverSideDown

23

另一种方法(也许是出于许多原因最糟糕的方法)是:

[UIView animateWithDuration:0.0 delay:5.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
} completion:^(BOOL finished) {
    //do stuff here
}];

16

如果你需要一个特定的较长延迟,上面的解决方案就可以正常工作。我已经成功地使用了 @nick 的方法。

但是,如果你只想让你的代码块在主循环的下一次迭代期间运行,你甚至可以进一步缩减代码量,仅需使用以下内容:

[[NSOperationQueue mainQueue] addOperationWithBlock:aBlock];

这就像使用 performSelector: 但是延迟时间设置为0.0f


4
+1,但现有performSelector的优点在于它具有取消的功能。虽然在零延迟的情况下这不那么重要,但即使如此,解决潜在的竞争问题仍然是有用的。 - JLundell

11

我使用了类似这样的代码:

double delayInSeconds = 0.2f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
      //whatever you wanted to do here...  
    });

这是正确的解决方案,可能看起来很复杂,但在输入dispatch_after时,Xcode会自动完成模式。 - malhal

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接