Time between two actions - objective-c

Trying to record the time it takes for a user to press one button, and then the next.
Here's part of my .m file... I can't get theDate to hold its variable (The start time) All that happens is I get 0.000 seconds in the log & text label.
- (IBAction)start:(id)sender
{
NSDate *theDate = [[NSDate date] init];
}
- (IBAction)stop:(id)sender
{
NSTimeInterval timeInterval = [theDate timeIntervalSinceNow];
NSString *time = [NSString stringWithFormat:#"%f", timeInterval];
_timeLabel.text = time;
NSLog(time);
}
I also have
#property(retain, nonatomic) NSDate *theDate;
in the header file and
#synthesize theDate;
in the top of the .m file.
Thanks!
BTW I'm trying to go off of this post: Getting the time elapsed (Objective-c)

NSDate *theDate = [[NSDate date] init];
Creates a local variable so it doesn't use your property. In addition, as #Catfish_Man pointed out, [[NSDate date] init] should just be [NSDate date].
In addition, you should use self.theDate to use the property.
- (IBAction)start:(id)sender
{
self.theDate = [NSDate date];
}
- (IBAction)stop:(id)sender
{
NSTimeInterval timeInterval = [self.theDate timeIntervalSinceNow];
NSString *time = [NSString stringWithFormat:#"%f", timeInterval];
_timeLabel.text = time;
NSLog(time);
}

You are redefining date with a local scope in
- (IBAction)start:(id)sender
You should be using
self.date = [NSDate date];

You can get really fine timing (seconds.parts of seconds) using this StopWatch class. It uses the high-precision timer in the iPhone. Using NSDate will only get you second(s) accuracy.
StopWatch.h
#import <Foundation/Foundation.h>
#interface StopWatch : NSObject
{
uint64_t _start;
uint64_t _stop;
uint64_t _elapsed;
}
-(void) Start;
-(void) Stop;
-(void) StopWithContext:(NSString*) context;
-(double) seconds;
-(NSString*) description;
+(StopWatch*) stopWatch;
-(StopWatch*) init;
#end
StopWatch.m
#import "StopWatch.h"
#include <mach/mach_time.h>
#implementation StopWatch
-(void) Start
{
_stop = 0;
_elapsed = 0;
_start = mach_absolute_time();
}
-(void) Stop
{
_stop = mach_absolute_time();
if(_stop > _start)
{
_elapsed = _stop - _start;
}
else
{
_elapsed = 0;
}
_start = mach_absolute_time();
}
-(void) StopWithContext:(NSString*) context
{
_stop = mach_absolute_time();
if(_stop > _start)
{
_elapsed = _stop - _start;
}
else
{
_elapsed = 0;
}
NSLog([NSString stringWithFormat:#"[%#] Stopped at %f",context,[self seconds]]);
_start = mach_absolute_time();
}
-(double) seconds
{
if(_elapsed > 0)
{
uint64_t elapsedTimeNano = 0;
mach_timebase_info_data_t timeBaseInfo;
mach_timebase_info(&timeBaseInfo);
elapsedTimeNano = _elapsed * timeBaseInfo.numer / timeBaseInfo.denom;
double elapsedSeconds = elapsedTimeNano * 1.0E-9;
return elapsedSeconds;
}
return 0.0;
}
-(NSString*) description
{
return [NSString stringWithFormat:#"%f secs.",[self seconds]];
}
+(StopWatch*) stopWatch
{
StopWatch* obj = [[[StopWatch alloc] init] autorelease];
return obj;
}
-(StopWatch*) init
{
[super init];
return self;
}
#end
The class has a static stopWatch method that returns an autoreleased object.
Once you call start, use the seconds method to get the elapsed time. Call start again to restart it. Or stop to stop it. You can still read the time (call seconds) anytime after calling stop.
Example In A Function (Timing call of execution)
-(void)SomeFunc
{
StopWatch* stopWatch = [StopWatch stopWatch];
[stopWatch Start];
... do stuff
[stopWatch StopWithContext:[NSString stringWithFormat:#"Created %d Records",[records count]]];
}
There is an equivalent version in C++ (for .mm or .cpp implementations) if you need it. You can find that here.
Even if you don't use the class specifically, the general technique will allow you to get high accurate time differences for events.
NOTE I could cache the mach_timebase_info_data_t if it is the first time and save the call to get it. This is older code and does not have that (minor) optimization.

Related

Calling a method every second doesn't work

I am trying to schedule a GNUStep Objective-C method call to run every second for a variable number of seconds. I am trying to use NSTimer to schedule the method call, but the handler method never gets called.
Here is my code:
Timer.m:
- (id) init {
self = [super init];
if(self) {
_ticks = 0;
}
return self;
}
- (void) startWithTicks: (unsigned int) ticks {
_ticks = ticks; //_ticks is an unsigned int instance variable
if(_ticks > 0) {
[NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: #selector(onTick:)
userInfo: nil
repeats: YES];
}
}
- (void) onTick: (NSTimer*) timer {
NSLog(#"tick");
_ticks--;
if(_ticks == 0) {
[timer invalidate];
timer = nil;
}
}
main.m:
int main(int argc, const char* argv[]) {
Timer* t = [[Timer alloc] init];
NSLog(#"Setting timer");
[t startWithTicks: 3];
usleep(5000);
NSLog(#"End of timer");
return 0;
}
I would expect the output to be
Setting timer
tick
tick
tick
End of timer
However, the output is
Setting timer
End of timer
Why is this and how can I fix it?
The timer won't run while your thread is sleeping.
Your timer class code works fine if you're using it from a ViewController.
If instead you'd like to use it within the main method, you'll want to explicitly run the mainRunLoop. Try adjusting your main method to this:
int main(int argc, char * argv[]) {
Timer *timer = [[Timer alloc] init];
NSLog(#"Setting Timer");
[timer startWithTicks:3];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(#"End of Timer");
return 0;
}
to run the mainRunLoop running for 3 seconds, which should produce your desired output.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
https://developer.apple.com/documentation/foundation/nsrunloop

Stub -[NSDate init]

Stubbing NSDate to return a mock date can easily be done using category except for -[NSDate init]. -[NSDate init] is not called unlike other methods. class_addMethod does not help. method_exchangeImplementations, method_setImplementation on -[NSDate init] actually change -[NSObject init] but no effect on -[NSDate init].
[NSDate setMockDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0]];
NSDate *date1 = [NSDate date];
NSLog(#"%#", date1);
NSLog(#"%.0f", [date1 timeIntervalSinceNow]);
// _replacement_Method is not called!
NSDate *date2 = [[NSDate alloc] init];
NSLog(#"%#", date2);
NSLog(#"%.0f", [date2 timeIntervalSinceNow]);
// _replacement_Method is called
NSObject *object = [[NSObject alloc] init];
NSLog(#"%#", object);
// A class with empty implementation to test inherited init from NSObject
// _replacement_Method is called by -[MyObject init]
MyObject *myobject = [[MyObject alloc] init];
NSLog(#"%#", myobject);
The output is
2001-01-01 00:00:00 +0000
-0
2014-11-26 14:43:26 +0000
438705806
<NSObject: 0x7fbc50e19d90>
<MyObject: 0x7fbc50e4ad30>
NSDate+Mock.m
#import "NSDate+Mock.h"
#import <mach/clock.h>
#import <mach/mach.h>
#import <objc/runtime.h>
static NSTimeInterval sTimeOffset;
static IMP __original_Method_Imp;
id _replacement_Method(id self, SEL _cmd)
{
return ((id(*)(id,SEL))__original_Method_Imp)(self, _cmd);
}
#implementation NSDate (Mock)
+ (NSObject *)lock
{
static dispatch_once_t onceToken;
static NSObject *lock;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc] init];
});
return lock;
}
+ (void)setMockDate:(NSDate *)date
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method m1 = class_getInstanceMethod([NSDate class], #selector(init));
Method m2 = class_getInstanceMethod([NSDate class], #selector(initMock));
// method_exchangeImplementations(m1, m2);
// class_addMethod([NSDate class], #selector(init), (IMP)_replacement_Method, "##:");
__original_Method_Imp = method_setImplementation(m1, (IMP)_replacement_Method);
});
#synchronized([self lock]) {
sTimeOffset = [date timeIntervalSinceReferenceDate] - [self trueTimeIntervalSinceReferenceDate];
}
}
+ (NSTimeInterval)mockTimeOffset
{
#synchronized([self lock]) {
return sTimeOffset;
}
}
+ (NSTimeInterval)trueTimeIntervalSinceReferenceDate
{
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
NSTimeInterval now = mts.tv_sec + mts.tv_nsec * 1e-9 - NSTimeIntervalSince1970;
return now;
}
+ (NSTimeInterval)timeIntervalSinceReferenceDate
{
return [self trueTimeIntervalSinceReferenceDate] + [self mockTimeOffset];
}
+ (instancetype)date
{
return [[NSDate alloc] initWithTimeIntervalSinceNow:0];
}
+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs
{
return [[NSDate alloc] initWithTimeIntervalSinceNow:secs];
}
//- (instancetype)init
//{
// self = [super init];
// return self;
//}
//- (instancetype)initMock
//{
// self = nil;
// NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:0];
// return date;
//}
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs
{
return [self initWithTimeIntervalSinceReferenceDate:[NSDate timeIntervalSinceReferenceDate] + secs];
}
- (NSTimeInterval)timeIntervalSinceNow
{
NSTimeInterval t = [self timeIntervalSinceReferenceDate];
return t - [NSDate timeIntervalSinceReferenceDate];
}
#end
NSDate has to important characteristics:
It is a class cluster
It is immutable
In such a case, +alloc returns only a placeholder and you send -init… to that placeholder (of class __NSPlaceholderDate). Replacing -init (NSDate) has no effect, if -init (__NSPlaceholderDate or NSWhatever is implemented.)
This is, because +alloc cannot decide which (private) subclass to choose, because it has no parameters. (They are passed at the -init….)
You can
simply replace -init of __NSPlaceholderDate
replace -init of what +alloc returns.
replace +alloc to return your private placeholder and overwrite -init in it.
If you need mock dates e.g. in your tests, consider instantiating the NSDate objects with the Factory pattern and replacing the factory for production or tests. This way only your own classes end up with the mock dates and you don't have to worry about accidentally replacing methods that may be used by Apple's frameworks.

Using NSProgress with nested NSOperations

I've been investigating NSProgress but have found the existing documentation, class reference and tutorials to be lacking. I'm mainly wondering if my NSProgress is applicable to my use case. The class reference documentation alternatively refers to suboperations or subtasks, I may be mistaken but I interpreted suboperations to mean a case where an NSOperation manages a group of other NSOperations. An example of my use case is as follows:
Create an Upload All Items in Group operation for each group that exists.
Add each of these operations to an NSOperationQueue.
Each Upload All Items in Group operation will create an Upload Item operation for each item in their group. These all get added to an NSOperationQueue managed by the operation.
I would have expected NSProgress to support this, and allow me to propagate progress from the nested operations (Upload Item operation) to the parent operation, and then finally to the main thread and the UI. But I've had difficulty implementing this, it seems as though NSProgress is meant more for long operations that execute all their code on one background thread, but have separate "sections" that make it easy to determine when progress has been made, if this is the case then the use of the term suboperation is a bit misleading as it brings to mind the use of nested NSOperations.
Thank you for any help you can provide, and let me know if additional details are needed.
NSProgress knows nothing about NSOperations -- the two things are orthogonal -- but that doesn't mean it can't be used with them. The idea behind nesting NSProgress "tasks" is that the inner task doesn't know anything about the outer task, and the outer task doesn't need direct access to the inner task's NSProgress to pull in updates for it. I cooked up a little example:
// Outer grouping
NSProgress* DownloadGroupsOfFiles(NSUInteger numGroups, NSUInteger filesPerGroup)
{
// This is the top level NSProgress object
NSProgress* p = [NSProgress progressWithTotalUnitCount: numGroups];
for (NSUInteger i = 0; i < numGroups; ++i)
{
// Whatever DownloadFiles does, it's worth "1 unit" to us.
[p becomeCurrentWithPendingUnitCount: 1];
DownloadFiles(filesPerGroup);
[p resignCurrent];
}
return p;
}
// Inner grouping
void DownloadFiles(NSUInteger numberOfFiles)
{
NSProgress* p = [NSProgress progressWithTotalUnitCount: numberOfFiles];
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
// Make the op queue last as long as the NSProgress
objc_setAssociatedObject(p, NULL, opQueue, OBJC_ASSOCIATION_RETAIN);
// For each file...
for (NSUInteger i = 0; i < numberOfFiles; ++i)
{
// Whatever this DownloadOperation does is worth 1 "unit" to us.
[p becomeCurrentWithPendingUnitCount: 1];
// Make the new operation
MyDownloadOperation* op = [[MyDownloadOperation alloc] initWithName: [NSString stringWithFormat: #"File #%#", #(i+1)]];
[opQueue addOperation: op];
[p resignCurrent];
}
}
// And then the DownloadOperation might look like this...
#interface MyDownloadOperation : NSOperation
#property (nonatomic, readonly, copy) NSString* name;
- (id)initWithName: (NSString*)name;
#end
#implementation MyDownloadOperation
{
NSProgress* _progress;
NSString* _name;
}
- (id)initWithName:(NSString *)name
{
if (self = [super init])
{
_name = [name copy];
// Do this in init, so that our NSProgress instance is parented to the current one in the thread that created the operation
_progress = [NSProgress progressWithTotalUnitCount: 1];
}
return self;
}
- (void)dealloc
{
_name = nil;
_progress = nil;
}
- (void)main
{
// Fake like we're doing something that takes some time
// Determine fake size -- call it 768K +- 256K
const NSUInteger size = 512 * 1024 + arc4random_uniform(512*1024);
const NSUInteger avgBytesPerSec = 1024 * 1024;
const NSTimeInterval updatePeriod = 1.0/60.0;
// Make sure all the updates to the NSProgress happen on the main thread
// in case someone is bound to it.
dispatch_async(dispatch_get_main_queue(), ^{
_progress.totalUnitCount = size;
_progress.completedUnitCount = 0;
});
NSUInteger bytesRxd = 0;
do
{
// Sleep for a bit...
usleep(USEC_PER_SEC * updatePeriod);
// "Receive some data"
NSUInteger rxdThisTime = updatePeriod * avgBytesPerSec;
// Never report more than all the bytes
bytesRxd = MIN(bytesRxd + rxdThisTime, size);
// Update on the main thread...
dispatch_async(dispatch_get_main_queue(), ^{
[_progress setCompletedUnitCount: bytesRxd];
});
} while (bytesRxd < size);
}
#end
One thing to note is that if NSProgress is being used to convey status to the UI, then you will want to make sure that every time you update the NSProgress object, you do so from the main thread, otherwise you'll get lots of weird crashes.
Alternately you could just use NSURLConnection to download files, and then have a delegate like this:
#interface MyURLConnectionProgressReporter : NSObject <NSURLConnectionDownloadDelegate>
#property (nonatomic, readwrite, assign) id<NSURLConnectionDownloadDelegate> delegate;
#end
NSProgress* DownloadABunchOfFiles(NSArray* arrayOfURLs)
{
arrayOfURLs = arrayOfURLs.count ? arrayOfURLs : #[ [NSURL URLWithString: #"http://www.google.com"] ];
NSProgress* p = [NSProgress progressWithTotalUnitCount: arrayOfURLs.count];
for (NSURL* url in arrayOfURLs)
{
[p becomeCurrentWithPendingUnitCount: 1];
MyURLConnectionProgressReporter* delegate = [[MyURLConnectionProgressReporter alloc] init];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: delegate];
[conn start];
[p resignCurrent];
}
return p;
}
#implementation MyURLConnectionProgressReporter
{
NSProgress* _progress;
}
static void EnsureMainThread(dispatch_block_t block);
- (id)init
{
if (self = [super init])
{
_progress = [NSProgress progressWithTotalUnitCount: 1];
EnsureMainThread(^{
_progress.kind = NSProgressKindFile;
[_progress setUserInfoObject:NSProgressFileOperationKindDownloading forKey:NSProgressFileOperationKindKey];
});
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
id retVal = [super forwardingTargetForSelector:aSelector];
if (!retVal && [self.delegate respondsToSelector: _cmd])
{
retVal = self.delegate;
}
return retVal;
}
- (void)p_updateWithTotalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress on the main thread...
EnsureMainThread(^{
if (!expectedTotalBytes)
_progress.totalUnitCount = -1;
else
_progress.totalUnitCount = MAX(_progress.totalUnitCount, expectedTotalBytes);
_progress.completedUnitCount = totalBytesWritten;
});
}
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress
[self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
// Then call on through to the other delegate
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connection:connection didWriteData:bytesWritten totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
}
}
- (void)connectionDidResumeDownloading:(NSURLConnection *)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress
[self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
// Then call on through to the other delegate
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connectionDidResumeDownloading:connection totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
}
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL
{
// We're done, so we want (_progress.completedUnitCount == _progress.totalUnitCount)
EnsureMainThread(^{
_progress.completedUnitCount = _progress.totalUnitCount;
});
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connectionDidFinishDownloading:connection destinationURL:destinationURL];
}
}
static void EnsureMainThread(dispatch_block_t block)
{
if (!block)
return;
else if ([NSThread isMainThread])
block();
else
dispatch_async(dispatch_get_main_queue(), block);
}
#end
Hope that helps.

Stopwatch using NSTimer incorrectly includes paused time in display

This is my code for an iPhone stopwatch. It works as expected and stops and resumes when the buttons are clicked.
When I hit "Stop", however, the timer won't stop running in the background, and when I hit "Start" to resume it, it will update the time and skip to where it is currently instead of resuming from the stopped time.
How can I stop the NSTimer? What is causing this to occur?
#implementation FirstViewController;
#synthesize stopWatchLabel;
NSDate *startDate;
NSTimer *stopWatchTimer;
int touchCount;
-(void)showActivity {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
[dateFormatter release];
}
- (IBAction)onStartPressed:(id)sender {
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
touchCount += 1;
if (touchCount > 1)
{
[stopWatchTimer fire];
}
else
{
startDate = [[NSDate date]retain];
[stopWatchTimer fire];
}
}
- (IBAction)onStopPressed:(id)sender {
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self showActivity];
}
- (IBAction)reset:(id)sender; {
touchCount = 0;
stopWatchLabel.text = #"00:00.00";
}
Your calculation of the current display always uses the original start time of the timer, so the display after pausing includes the interval that the timer was paused.
The easiest thing to do would be to store another NSTimeInterval, say secondsAlreadyRun, when the timer is paused, and add that to the time interval you calculate when you resume. You'll want to update the timer's startDate every time the timer starts counting. In reset:, you would also clear out that secondsAlreadyRun interval.
-(void)showActivity:(NSTimer *)tim {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
// Add the saved interval
timeInterval += secondsAlreadyRun;
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
[dateFormatter release];
}
- (IBAction)onStartPressed:(id)sender {
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
target:self
selector:#selector(showActivity:)
userInfo:nil
repeats:YES];
// Save the new start date every time
startDate = [[NSDate alloc] init]; // equivalent to [[NSDate date] retain];
[stopWatchTimer fire];
}
- (IBAction)onStopPressed:(id)sender {
// _Increment_ secondsAlreadyRun to allow for multiple pauses and restarts
secondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startDate];
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[startDate release];
[self showActivity];
}
- (IBAction)reset:(id)sender; {
secondsAlreadyRun = 0;
stopWatchLabel.text = #"00:00.00";
}
Don't forget to release that startDate somewhere appropriate! Also keep in mind that the documented NSTimer interface is for the method you give it to accept one argument, which will be the timer itself. It seems to work without that, but why tempt fate?
Finally, since you're using that NSDateFormatter so much, you might want to consider making it an ivar or put it in static storage in showActivity:, like so:
static NSDateFormatter * dateFormatter = nil;
if( !dateFormatter ){
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
}
So, when the user presses stop, and then start again, you aren't resetting the start time. But when you update the label, you are basing that on the total elapsed time from the original start time to the current time.
So if you run the timer for 10 seconds, stop, wait 10 seconds, and then start again, the timer will show 00:20.00 and start counting again from there.
What you want to do is reset the start time each time the user starts the clock, but then add the elapsed times of all previous runs as well. Or something similar.
BTW, you are leaking the start time every time you reset it now. Minor bug.
EDIT: looks like #Josh Caswell was thinking the same thing, but he types a LOT faster. :)
Are you using ARC or not?
If you are using ARC, it looks like you arent using a _strong reference. If you aren't using ARC, it doesn't looking you are retaining a reference to the timer.
I'm posting this from mobile so might be missing something.
EDIT: just noticed you were using release elsewhere, so I'll assume no ARC. You need to retain the timer after setting it to be able to access it later and invalidate.
You can use NSTimeInterval instead of timer. I have a functional code to pause and stop the timer.
#interface PerformBenchmarksViewController () {
int currMinute;
int currSecond;
int currHour;
int mins;
NSDate *startDate;
NSTimeInterval secondsAlreadyRun;
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
running = false;
}
- (IBAction)StartTimer:(id)sender {
if(running == false) {
//start timer
running = true;
startDate = [[NSDate alloc] init];
startTime = [NSDate timeIntervalSinceReferenceDate];
[sender setTitle:#"Pause" forState:UIControlStateNormal];
[self updateTime];
}
else {
//pause timer
secondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startDate];
startDate = [[NSDate alloc] init];
[sender setTitle:#"Start" forState:UIControlStateNormal];
running = false;
}
}
- (void)updateTime {
if(running == false) return;
//calculate elapsed time
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsed = secondsAlreadyRun + currentTime - startTime;
// extract out the minutes, seconds, and hours of seconds from elapsed time:
int hours = (int)(mins / 60.0);
elapsed -= hours * 60;
mins = (int)(elapsed / 60.0);
elapsed -= mins * 60;
int secs = (int) (elapsed);
//update our lable using the format of 00:00:00
timerLabel.text = [NSString stringWithFormat:#"%02u:%02u:%02u", hours, mins, secs];
//call uptadeTime again after 1 second
[self performSelector:#selector(updateTime) withObject:self afterDelay:1];
}
Hope this will help. Thanks
A timer class I created in Swift for a timer program in which a counter is updated every second from a set time. Answered to illustrate the Swift solution and the NSTimer function.
The timer can be stopped and restarted; it will resume from where it stopped. Events can be intercepted by the delegate for start, stop, reset, end and second events. Just check the code.
import Foundation
protocol TimerDelegate {
func didStart()
func didStop()
func didReset()
func didEnd()
func updateSecond(timeToGo: NSTimeInterval)
}
// Inherit from NSObject to workaround Selector bug
class Timer : NSObject {
var delegate: TimerDelegate?
var defaultDuration: NSTimeInterval?
var isRunning: Bool {
get {
return self.timer != nil && timer!.valid
}
}
private var secondsToGo: NSTimeInterval = 0
private var timer: NSTimer?
init(defaultDuration: NSTimeInterval, delegate: TimerDelegate? = nil) {
self.defaultDuration = defaultDuration
self.delegate = delegate
super.init()
}
func start() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateTimer", userInfo: nil, repeats: true)
self.timer!.tolerance = 0.05
if delegate != nil { delegate!.didStart() }
}
func stop () {
self.timer?.invalidate()
self.timer = nil
if delegate != nil { delegate!.didStop() }
}
func reset() {
self.secondsToGo = self.defaultDuration!
if delegate != nil { delegate!.didReset() }
}
func updateTimer() {
--self.secondsToGo
if delegate != nil { delegate!.updateSecond(self.secondsToGo) }
if self.secondsToGo == 0 {
self.stop()
if delegate != nil { delegate!.didEnd() }
}
}
}

Setter method based on user input

New to Objective-C and trying to complete a final project for a class. I have created a method to set a date based on user input and am running into some trouble with the setter. User will first have to select option to add new entry and then program should ask user to enter a date and set date based on that input.
#import <Foundation/Foundation.h>
#interface Planner : NSObject {
NSNumber *date;
}
-(void) setDate:(NSNumber *)newDate;
-(NSNumber *) date;
#end
#implementation Planner
-(void) setDate:(NSNumber *) newDate
{
date = [[NSNumber alloc] initWithInt: newDate];
NSLog(#"Enter date");
scanf("%i", newDate);
}
-(NSNumber *) date;
{
return date;
}
#end
int main (int argc, const char * argv[])
{
int userAction;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Planner *newPlanner = [[Planner alloc] init];
NSLog(#"Please enter 1 to add a new entry and 2 to update an existing entry");
scanf("%i", userAction);
if (userAction == 1) {
[newPlanner setDate];
}
else
NSLog(#"will update");
[pool drain];
return 0;
}
-(void) setDate:(NSNumber *) newDate;
setDate is supposed to receive an argument of type NSNumber*. But you are calling the method with out passing any parameter.
[newPlanner setDate]; // [newPlanner setDate: shouldPassNSNumberArgument];
Like -
[newPlanner setDate:[NSNumber numberWithInt:10] ];