Objective c NSTimer';未调用的操作方法
我有以下代码来创建一个Objective c NSTimer';未调用的操作方法,objective-c,ios,cocoa-touch,nstimer,Objective C,Ios,Cocoa Touch,Nstimer,我有以下代码来创建一个NSTimer,它应该在每次触发时更新标签: .h文件 @interface Game : UIViewController { NSTimer *updateTimer; UILabel *testLabel; int i; } -(void)GameUpdate; @end .m文件 @implementation Game -(void)GameUpdate { i++; NSString *textToDisplay
NSTimer
,它应该在每次触发时更新标签:
.h文件
@interface Game : UIViewController
{
NSTimer *updateTimer;
UILabel *testLabel;
int i;
}
-(void)GameUpdate;
@end
.m文件
@implementation Game
-(void)GameUpdate
{
i++;
NSString *textToDisplay = [[NSString alloc] initWithFormat:@"Frame: %d", i];
[testLabel setText:textToDisplay];
NSLog(@"Game Updated");
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];
}
//other methods (viewDidUnload, init method, etc.)
@end
当我运行它时,顶部会出现一个标签,上面写着“0”,但不会改变。这让我相信我在如何设置NSTimer
时遗漏了一些东西。我错过了什么
我使用断点和(如您所见)日志来查看该方法是否正在实际运行,而不是其他错误。您的回调必须具有以下签名:
-(void)GameUpdate:(NSTimer *)timer
这在文档中是明确的。设置计时器时的@selector()引用应该是@selector(GameUpdate:)
(注意后面的:
)
试试看。为了防止有人无意中发现这一点,我想指出:
[[NSString alloc] initWithFormat:@"Frame: %d", i];
需要内存管理
安全替换为:
[NSString stringWithFormat:@"Frame: %d", i];
同样的效果,但不需要内存管理
另外,在写这篇文章的时候,我无法对原文发表评论,所以我补充了这一点作为回答
编辑:正如下面指出的,这与ARC的广泛使用不再相关。我也遇到过类似的问题,它有不同的根本原因,与运行循环有关。值得注意的是,当您使用代码计划计时器时:
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];
计时器将使用当前线程的runLoop进行调度。在您的情况下,因为您在viewDidLoad中进行了此调用,所以它是主线程,所以您可以继续
但是,如果您使用主线程以外的线程调度计时器,那么它将在该线程的运行循环上调度,而不是在主线程上调度。这很好,但是在辅助线程上,您负责创建和启动初始运行循环,因此如果您没有这样做,您的回调将永远不会被调用
解决方案是启动辅助线程的runLoop,或者将计时器启动分派到主线程
发送:
dispatch_async(dispatch_get_main_queue(), ^{
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];
});
要启动runloop,请执行以下操作:
使用您选择的API创建线程后,调用CFRunLoopGetCurrent()为该线程分配初始运行循环。以后对CFRunLoopGetCurrent的任何调用都将返回相同的运行循环
CFRunLoopGetCurrent();
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.01428 target:self selector:@selector(GameUpdate) userInfo:nil repeats:YES];
对于NSTimer,我遇到了一个稍微不同的问题—在UITableView滚动期间忽略了预定的方法调用。 计时器已从主线程启动。将计时器显式添加到主运行循环解决了该问题
[[NSRunLoop mainRunLoop] addTimer:playbackTimer forMode:NSRunLoopCommonModes];
这里找到了解决方案
UPD:CADisplayLink
更适合更新用户界面。
根据官方文件,CADisplayLink是:
类,表示绑定到显示vsync的计时器
并且可以很容易地实现,如:
playbackTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateUI)];
[playbackTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
然后像
if (playbackTimer) {
[playbackTimer removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
playbackTimer = nil;
}
那么,该方法实际运行吗?你看到“游戏更新”不止一次吗?谢谢你的回答。我也有同样的问题。谢谢你,伙计,在header的文档中一个字也没有!苹果真是太遗憾了。你没有在那里真正解释“需要内存管理”
initWithFormat
返回保留的实例,而stringWithFormat
返回自动删除的实例。大多数在方法名(arrayWithArray
etc)中描述返回值的方法在整个过程中都遵循此约定。这与ARC无关了!