0
#import "ActivityViewController.h"

@interface ActivityViewController ()
@property (weak, nonatomic) IBOutlet UIView *clockView;
@property (weak , nonatomic) NSTimer *timer;


@property (weak, nonatomic) IBOutlet UILabel *hoursLabel;
@property (weak, nonatomic) IBOutlet UILabel *minutesLabel;

@property (weak, nonatomic) IBOutlet UILabel *secondsLabel;

@property (weak, nonatomic) IBOutlet UILabel *miliLabel;




@end




@implementation ActivityViewController



- (void)viewDidLoad {
    [super viewDidLoad];
    
    
}

NSDate *start;

- (IBAction)pause:(UIButton *)sender {
    [self.timer invalidate];
}


-(void)startTimer {
    start = [[NSDate alloc] init];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(count) userInfo:nil repeats:true];
    
    
    
  
    
    
}

- (IBAction)startCounting:(UIButton *)sender {
    [self startTimer];
}

-(void)count {
    

    NSDate *now = [[NSDate alloc] init];
    
   
    
 
    
    NSTimeInterval interval = [now timeIntervalSinceDate:start];
    self.hoursLabel.text = [NSString stringWithFormat:@"%@", [self hourString:interval]];
    self.minutesLabel.text = [NSString stringWithFormat:@"%@", [self minuteString:interval]];
    self.secondsLabel.text = [NSString stringWithFormat:@"%@", [self secondString:interval]];
    self.miliLabel.text = [NSString stringWithFormat:@"%@", [self miliString:interval]];
        
 
    
}

-(NSString *)hourString:(NSTimeInterval)timeInterval {
    NSInteger interval = timeInterval;
    long hours = (interval / 3600);
    
    return [NSString stringWithFormat:@"%0.2ld", hours];
}

-(NSString *)minuteString:(NSTimeInterval)timeInterval {
    NSInteger interval = timeInterval;
    long minutes = (interval / 60) % 60;
    
    return [NSString stringWithFormat:@"%0.2ld", minutes];
}

-(NSString *)secondString:(NSTimeInterval)timeInterval {
    NSInteger interval = timeInterval;
    long seconds = interval % 60;
    
    return [NSString stringWithFormat:@"%0.2ld", seconds];
}

-(NSString *)miliString:(NSTimeInterval)timeInterval {
 
    NSInteger ms = (fmod(timeInterval, 1) * 100);
    return [NSString stringWithFormat:@"%0.2ld", ms];
}











@end

This my whole implementation of the view controller

I try to make a stopwatch, but when I try to make this in objective-C updating UI in miliseconds slows down after 3 seconds and frozes simulator and computer after 6 seconds. Is there any solution? In swift it works very smoothly but when it comes to obj-c I have problem.

Selçuk Yıldız
  • 539
  • 1
  • 5
  • 14
  • Hi - as a test / start, remove the dispatch_async and just run that code as is and also set the increment to 0.1 and see how it goes. I suspect there are other UI updates that also clog the system. Then rework it as in my post to use the internal time in stead of the timer and then, for best accuracy, get the time as in that link in my post. – skaak Oct 13 '20 at 18:49
  • thanks for your answer. even remove dispatch_async and I increment 0.1 still same result, when I make it 0.1 it starts slowing not at 3 seconds but in 60 seconds, I have made same thing in swift it works great but in oc I have this problem – Selçuk Yıldız Oct 13 '20 at 20:07
  • I tried it on real device and it works smoothly updates UI. problem is just in simulator. – Selçuk Yıldız Oct 13 '20 at 20:18
  • Ok - the code you give, I think is trimmed down. I think there are some heavy UI updates that you do not give that is causing the trouble. Even so, look at the issues I raise. I was thinking. E.g. if you switch from your stopwatch to check email and back the stopwatch would 'loose' that time so you have to do as I say and use the internal clock. – skaak Oct 13 '20 at 22:51
  • yes I used timeInterval but same issue in simulator. Problem occurs in real device after 2-3 minutes. And there is not any other ui updates or any other task in the viewcontroller – Selçuk Yıldız Oct 13 '20 at 22:53
  • Hmmmm really strange. The timeInterval here is not the issue. That will make it more accurate but will not solve the freeze issue. Can you post the updated code, where you removed the dispatch_async just so I can eyeball it again. – skaak Oct 13 '20 at 22:55
  • Also, in your startTimer message, you can add (first line) self.timer.invalidate; This will stop any timer that might be running so if you press the button to start it multiple times you will have multiple timers. – skaak Oct 13 '20 at 22:58
  • And, quoting the manual, make the count exactly like this ```- (void)count:(NSTimer *)timer``` not sure this will solve anything but let's try to be precise here because we get this funny issue. – skaak Oct 13 '20 at 23:01
  • I updated the code. but it is all about changing uilabel text – Selçuk Yıldız Oct 13 '20 at 23:06
  • Ok - you need to fix up count as I said earlier, it does not have the correct signature. Also invalidate the current timer, also as I said earlier, as pretty soon you will have more than one running timer. Then make ```start``` a ```strong``` ivar. I think you also need to make the timer a ```strong``` ivar. Updating labels will not be the issue, you can do that a thousand times a second and it should work ... – skaak Oct 13 '20 at 23:20
  • okay thanks a lot. I tried all you have said but same issue even in the device after 2 minutes updating the miliseconds labels slows down. – Selçuk Yıldız Oct 13 '20 at 23:32

2 Answers2

1

Several things ... you dispatch on the main queue but you are already on the main queue so you are swamping the main queue. Also, you dispatch async so the queue gets clogged pretty soon I think. Still I would think the hardware should be able to handle it actually so I am not entirely sure why it freezes, but I think you need to do it a bit better.

Also, importantly, if you build a stopwatch you should not count yourself. Sure, use a timer to regularly update the stopwatch, but in stead of counting yourself you should use the wall clock for the time. The way you do it now will be very inaccurate and the inaccuracies will accumulate over time.

At present you fire every 10 millis. Maybe fire every 100 and then update your stopwatch but read from the device's internal clock. This in itself is problematic, see How can I get the Correct Current Time in iOS? for some ideas on how to do that.

When the stopwatch starts note the time e.g.

NSDate * start = NSDate.now;

and when the timer fires calculate the difference from this start time using the current time, again with something like

NSDate * now = NSDate.now;

and update your stopwatch with the difference between the two. This will be both easier and more accurate BUT see that link as it is also needs some improvement and is just a starting point for now.

skaak
  • 2,988
  • 1
  • 8
  • 16
1

I created a new default iOS project in the latest Xcode and copied and pasted and slightly fixed your code - see below. I just wired it up to the default view controller that Xcode creates as part of the project.

enter image description here

I am running this on the oldest mac mini known to man and working on another project while running this one and it gave no trouble at all. See the screenshot. Even after 5m it was still running along just fine. I continued work elsewhere no problem, nothing slowed down. I am sure it must be some other funny issue?

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIView  * clockView;
@property (weak, nonatomic) IBOutlet UILabel * hoursLabel;
@property (weak, nonatomic) IBOutlet UILabel * minutesLabel;
@property (weak, nonatomic) IBOutlet UILabel * secondsLabel;
@property (weak, nonatomic) IBOutlet UILabel * miliLabel;

@property (strong, nonatomic) NSTimer * timer;
@property (strong, nonatomic) NSDate  * start;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (IBAction)pause:(UIButton *)sender
{
    self.timer.invalidate;
}

-(void)startTimer
{
    self.start = [[NSDate alloc] init];
    self.timer.invalidate;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01
                              target:self
                            selector:@selector(count:)
                            userInfo:nil
                             repeats:true];
}

- (IBAction)startCounting:(UIButton *)sender
{
    [self startTimer];
}

- ( void ) count:( NSTimer * ) timer
{
    NSDate *now = [[NSDate alloc] init];

    NSTimeInterval interval = [now timeIntervalSinceDate:self.start];
    self.hoursLabel.text = [NSString stringWithFormat:@"%@", [self hourString:interval]];
    self.minutesLabel.text = [NSString stringWithFormat:@"%@", [self minuteString:interval]];
    self.secondsLabel.text = [NSString stringWithFormat:@"%@", [self secondString:interval]];
    self.miliLabel.text = [NSString stringWithFormat:@"%@", [self miliString:interval]];
}

-(NSString *)hourString:(NSTimeInterval)timeInterval {
    NSInteger interval = timeInterval;
    long hours = (interval / 3600);

    return [NSString stringWithFormat:@"%0.2ld", hours];
}

-(NSString *)minuteString:(NSTimeInterval)timeInterval {
    NSInteger interval = timeInterval;
    long minutes = (interval / 60) % 60;

    return [NSString stringWithFormat:@"%0.2ld", minutes];
}

-(NSString *)secondString:(NSTimeInterval)timeInterval {
    NSInteger interval = timeInterval;
    long seconds = interval % 60;

    return [NSString stringWithFormat:@"%0.2ld", seconds];
}

-(NSString *)miliString:(NSTimeInterval)timeInterval {

    NSInteger ms = (fmod(timeInterval, 1) * 100);
    return [NSString stringWithFormat:@"%0.2ld", ms];
}

@end

This is some other issue I am sure ... I assume your ActivityViewController just derives straight from a normal UIViewController and that your clockView is not doing anything funny.

Anyhow, see if this helps but I think the issue is elsewhere.

skaak
  • 2,988
  • 1
  • 8
  • 16
  • Been running it now at 0.001!! increment for more than 10m while running another project in another simulator, all on a mac mini! No slow down, animations in the other project are running smoothly, no trouble at all. Oh well ... hope you can solve this, let me know if you make any progress ... – skaak Oct 14 '20 at 00:31
  • oh my god, I found the problem, I draw gradient over my canvas view in viewdidlayoutsubviews method. and this means it needs to be draw every miliseconds update. yes bro you are right to say that it is funny :) – Selçuk Yıldız Oct 14 '20 at 07:31
  • Great !!!!!!!!! This was such a puzzle I am so glad you fixed it - and thanks for sharing. – skaak Oct 14 '20 at 07:32
  • Maybe use same approach in viewDidLayoutSubviews ... first check if timer is active (self.timer != nil) and then calculate time difference between current time and previous time you updated the gradient. So every time you update the gradient you also update an ivar to keep the time of the update. Then, every 2 seconds or so, you update the gradient ... anyhow, a bit off topic here but nice way to update the gradient without killing the UI. – skaak Oct 14 '20 at 07:34