【问题标题】:How can I detach a thread without calling the selector, or at least make a work-around?如何在不调用选择器的情况下分离线程,或者至少进行解决?
【发布时间】:2013-07-30 20:30:01
【问题描述】:

所以我有一个带有计时器的视图控制器。它有一个按钮。当视图第一次加载时,按钮应该说“开始”。

当它被点击时,“开始”->“暂停”。

再次点击,“暂停”->“继续”。

再次点击,“继续”->“暂停”。

因为我希望计时器准确,所以我将它从主线程中分离出来(我认为我选择了正确的方法,我希望能得到一些澄清)。但似乎将它与线程分离实际上调用了该方法......这使得按钮以“暂停”而不是开始。我该如何解决这个问题?

顺便说一下,testTask.showButtonValue 的默认值(加载)是 1。

- (void)viewDidLoad {
    [super viewDidLoad];

    [NSThread detachNewThreadSelector:@selector(startTimer:) toTarget:self withObject:nil];

    if (testTask.showButtonValue == 1) {
        [startButton setTitle:@"Start" forState:UIControlStateNormal];
    } else if (testTask.showButtonValue == 2) {
        [startButton setTitle:@"Pause" forState:UIControlStateNormal];
    } else if (testTask.showButtonValue == 3){
        [startButton setTitle:@"Resume" forState:UIControlStateNormal];
    }
}
-(IBAction)startTimer:(id)sender{
    if (testTask.showButtonValue == 1) {
        [startButton setTitle:@"Pause" forState:UIControlStateNormal];
        timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
        testTask.showButtonValue = 2;
    } else if (testTask.showButtonValue == 2) {
        [startButton setTitle:@"Resume" forState:UIControlStateNormal];
        [timer invalidate];
        timer = nil;
        testTask.showButtonValue = 3;
    } else if (testTask.showButtonValue == 3){
        [startButton setTitle:@"Pause" forState:UIControlStateNormal];
        timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
        testTask.showButtonValue = 2;
    }
}
-(void)timerAction:(NSTimer *)t
{
    if(testTask.timeInterval == 0)
    {
        if (self.timer)
        {
            [self timerExpired];
            [self.timer invalidate];
            self.timer = nil;
        }
    }
    else
    {
        testTask.timeInterval--;
    }
    NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
    NSString *string = [NSString stringWithFormat:@"%02u:%02u:%02u",
                        seconds / 3600, (seconds / 60) % 60, seconds % 60];
    timerLabel.text = string;
    NSLog(@"%f", testTask.timeInterval);
}

【问题讨论】:

    标签: iphone ios objective-c nstimer nsthread


    【解决方案1】:

    我建议以不同的方式解决这个问题:

    @interface SNDViewController ()
    @property (weak, nonatomic) IBOutlet UIButton *startButton;
    @property (weak, nonatomic) IBOutlet UILabel *timerLabel;
    @property (nonatomic, strong) NSTimer *timer;
    @property (nonatomic, assign) NSTimeInterval accumulativeTime;
    @property (nonatomic, assign) NSTimeInterval currentReferenceTime;
    @end
    
    @implementation SNDViewController
    

    编辑:当视图加载时,初始化self.accumulativeTime 并更新计时器标签。 accumulativeTime 变量的初始化应该在适当的 init* 方法中完成,例如initWithNibName:捆绑:。此时,您将从核心数据中读取计时器值。

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.accumulativeTime = 300;
        [self updateTimerLabelWithTotalTime:self.accumulativeTime];
    }
    
    - (IBAction)changeTimerState:(UIButton *)sender
    {
        if (self.timer == nil) {
            self.currentReferenceTime = [NSDate timeIntervalSinceReferenceDate];
            [self.timer invalidate];
            self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES];
        } else {
            //Pause the timer and track accumulative time.
            NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
            [self.timer invalidate];
            self.timer = nil;
            NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;
    

    编辑: 从 accumalitiveTime 中减去 timeSinceCurrentReference,因为这是一个倒数计时器。如有必要,还添加了保存到核心数据的注释。

            self.accumulativeTime -= timeSinceCurrentReference;
            [self updateTimerLabelWithTotalTime:self.accumulativeTime];
    
            //Optionally save self.accumulativeTime to core data for future use.
        }
    
        NSString *buttonTitle = (self.timer != nil) ? @"Pause" : @"Resume";
        [self.startButton setTitle:buttonTitle forState:UIControlStateNormal];
    }
    

    您不需要存储任何不必要的状态,例如showButtonValue 变量。相反,您可以根据 self.timer == nil 来决定是否暂停或恢复。如果没有计时器运行,则获取当前参考时间并启动一个新计时器。计时器计划每 0.01 秒触发一次,这有望使其精确到 0.1 秒。您永远不需要将按钮的标题更改为“开始”。它是“暂停”或“恢复”。 当定时器被用户暂停时,我们处理self.timer,并用最准确的时间更新定时器标签。

    - (void)updateTimer:(id)sender
    {
        NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
        NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;
    

    编辑: 从 self.accumulativeTime 中减去 timeSinceCurrentReference 以获得 totalTime(即 totalTime 随着时间的推移而减少)。

        NSTimeInterval totalTime = self.accumulativeTime - timeSinceCurrentReference;
    
        if (totalTime <= 0) {
            totalTime = 0;
            [self.timer invalidate];
            self.timer = nil;
    
            //What to do when we reach zero? For example, we could reset timer to 5 minutes:
            self.accumulativeTime = 300;
            totalTime = self.accumulativeTime;
            [self.startButton setTitle:@"Start" forState:UIControlStateNormal];
        }
    
        [self updateTimerLabelWithTotalTime:totalTime];
    }
    

    每次触发计时器时,我们都会通过找出nowself.currentReferenceTime 之间的差并将其添加到self.accumulativeTime 来获得总时间。

    - (void)updateTimerLabelWithTotalTime:(NSTimeInterval)totalTime
    {
        NSInteger hours = totalTime / 3600;
        NSInteger minutes = totalTime / 60;
        NSInteger seconds = totalTime;
        NSInteger fractions = totalTime * 10;
    
        self.timerLabel.text = [NSString stringWithFormat:@"%02u:%02u:%02u.%01u", hours, minutes % 60, seconds % 60, fractions % 10];
    }
    
    @end
    

    方法 - (IBAction)changeTimerState:(UIButton *)sender 由 UIButton 在“Touch Up Inside”事件 (UIControlEventTouchUpInside) 上调用。

    你不需要在 viewDidLoad 中做任何事情。

    此外,重要的是,这一切都在主线程上完成。如果有任何东西妨碍了主线程更新计时器标签,那么用户可见的文本可能是不准确的,但是当它被更新时,你可以确定它会再次准确。这取决于您的应用程序还在做什么。但是由于所有的 UI 更新都必须在主线程上完成,所以确实无法避免这一点。

    希望这会有所帮助。如果有任何不清楚的地方,请告诉我。

    (Xcode 项目在这里可用:https://github.com/sdods3782/TVTTableViewTest

    【讨论】:

    • 问题是,我们需要使用 testTask.timeInterval 属性来设置倒数计时器,而不是你现在正在做的事情,因为它存储在 Core Data 中并且需要保存 timeInterval。我该如何添加?
    • 另外,“您永远不需要将按钮的标题更改为“开始”。它是“暂停”或“恢复””是什么意思。第一次加载视图时应该是Start :O.
    • 最后,这是什么意思:“计时器计划每 0.01 秒触发一次,希望它可以精确到 0.1 秒。”这会导致大量内存使用吗?
    • 我的意思是当应用程序运行时,您不需要将按钮标题重置为“开始”,因此要么在 nib 中设置它,要么在 viewDidLoad 中设置它。不需要在任何处理方法中设置。
    • 每 0.01 秒触发一次计时器肯定不会占用更多内存。它将使用更多的 CPU 时间,但对于 updateTimer: 方法正在做什么,我不会担心。
    【解决方案2】:

    调用detachNewThreadSelector 将创建一个新线程并在调用后立即执行提到的选择器。

    要解决您的问题,请更改您的方法,例如:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        [NSThread detachNewThreadSelector:@selector(startTimer:) toTarget:self withObject:nil];
    }
    -(IBAction)startTimer:(id)sender
    {
        if (testTask.showButtonValue == 1) {
            [startButton setTitle:@"Start" forState:UIControlStateNormal];
            timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
            testTask.showButtonValue = 3;
        } else if (testTask.showButtonValue == 2) {
            [startButton setTitle:@"Resume" forState:UIControlStateNormal];
            [timer invalidate];
            timer = nil;
            testTask.showButtonValue = 3;
        } else if (testTask.showButtonValue == 3){
            [startButton setTitle:@"Pause" forState:UIControlStateNormal];
            timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
            testTask.showButtonValue = 2;
        }
    }
    

    【讨论】:

    • 这段代码中不是在主线程以外的线程中使用了UIKit吗?
    • 这行得通,但在我做出正确答案之前,您确实删除了 viewDidLoad 中的部分代码:O。当用户按下并再次进入视图时,它不会显示按钮的正确标签:/。我不确定如何解决这个问题..
    • 嗯..好像如果我启动它然后按回,然后再次转到视图,它实际上与线程分离了两次(或更多),这使得计时器下降快 2 倍 :O!!嗯……
    • @hfossli:是的,你是对的。不应在其他线程中操作 UI 元素。
    猜你喜欢
    • 2011-02-09
    • 1970-01-01
    • 2012-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多