Trying to follow the best practices of ReactiveCocoa to update my UI on the hour, every hour. This is what I've got:
NSDateComponents *components = [[[NSCalendar sharedCalendar] calendar] components:NSMinuteCalendarUnit fromDate:[NSDate date]];
// Generalization, I know (not every hour has 60 minutes, but bear with me).
NSInteger minutesToNextHour = 60 - components.minute;
RACSubject *updateEventSignal = [RACSubject subject];
[updateEventSignal subscribeNext:^(NSDate *now) {
// Update some UI
}];
[[[RACSignal interval:(60 * minutesToNextHour)] take:1] subscribeNext:^(id x) {
[updateEventSignal sendNext:x];
[[RACSignal interval:3600] subscribeNext:^(id x) {
[updateEventSignal sendNext:x];
}];
}];
This has some obvious flaws: manual subscription and sending, and it just "feels wrong." Any ideas on how to make this more "reactive"?
You can do this using completely vanilla operators. It's just a matter of chaining the two intervals together while still passing through both of their values, which is exactly what -concat: does.
I would rewrite the subject as follows:
RACSignal *updateEventSignal = [[[RACSignal
interval:(60 * minutesToNextHour)]
take:1]
concat:[RACSignal interval:3600]];
This may not give you super ultra exact precision (because there might be a minuscule hiccup between the two signals), but it should be Good Enough™ for any UI work.
Sounds like you need something like +interval:startingIn:.
With that thought, you could make your own version of +interval:startingIn: by slightly tweaking the implementation of +interval:.
+ (RACSignal *)interval:(NSTimeInterval)interval startingIn:(NSTimeInterval)delay {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
int64_t intervalInNanoSecs = (int64_t)(interval * NSEC_PER_SEC);
int64_t delayInNanoSecs = (int64_t)(delay * NSEC_PER_SEC);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, delayInNanoSecs), (uint64_t)intervalInNanoSecs, (uint64_t)0);
dispatch_source_set_event_handler(timer, ^{
[subscriber sendNext:[NSDate date]];
});
dispatch_resume(timer);
return [RACDisposable disposableWithBlock:^{
dispatch_source_cancel(timer);
dispatch_release(timer);
}];
}] setNameWithFormat:#"+interval: %f startingIn: %f", (double)interval, (double)delay];
}
With this in place, your code could be refactored to:
NSDateComponents *components = [[[NSCalendar sharedCalendar] calendar] components:NSMinuteCalendarUnit fromDate:[NSDate date]];
// Generalization, I know (not every hour has 60 minutes, but bear with me).
NSInteger minutesToNextHour = 60 - components.minute;
RACSubject *updateEventSignal = [[RACSignal interval:3600 startingIn:(minutesToNextHour * 60)];
[updateEventSignal subscribeNext:^(NSDate *now) {
// Update some UI
}];
Related
I have a label that displays the time; however, the time is not updated. The time is displayed, but it does not count up. The time at which the button was pressed is displayed and that time does not change. Here is my code
- (IBAction)startCamera:(id)sender
{
[self.videoCamera start];
NSDate *today = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSString *currentTime = [dateFormatter stringFromDate:today];
[dateFormatter setDateFormat:#"dd.MM.yyyy"];
NSString *currentDate = [dateFormatter stringFromDate:today];
for (int i = 1; i <= 10; i--) {
Label1.text = [NSString stringWithFormat:#"%#", currentTime];
Label2.text = [NSString stringWithFormat:#"%#", currentDate];
}
}
I tried a for loop but that does not update the time. Any suggestions?
UI updates are performed using an event loop that runs on the main thread. Your for-loop is hogging the main thread and never returns from you start function. Whatever you set in labelx.text never gets refreshed on screen because the run loop is waiting for your start function to finish.
You should read up on NSTimer to implement this using best practices.
There is also a way to do this using a delayed dispatch:
(sorry that this is in Swift, I don't know objective-C, but I'm sure you'll get the idea)
// add this function and call it in your start function
func updateTime()
{
// update label1 and label2 here
// also add an exit condition to eventually stop
let waitTime = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC ) // one second wait duration
dispatch_after(waitTime, dispatch_get_main_queue(), {self.updateTime() }) // updateTime() calls itself every 1 second
}
NSTimer works but its not very accurate.
When I need accurate timers I use CADisplaylink, especially when working with animations. This reduces visual stutter.
Using the display refresh is accurate and reliable. However you don't want to be doing heavy calculations using this method.
- (void)startUpdateLoop {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(update:)];
displayLink.frameInterval = 60;
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)update {
// set you label text here.
}
My company has created an enterprise app that is used out on the field.
We've discovered that our iPhone 5 does not function properly below 0*C.
One solution we're looking to experiment with is running heavy CPU-intensive tasks (equivalent to running a 3D game) which will add heat to the device (outside of the other external heat we're using to help them).
Using a laser temperature gauge indoors as a baseline, I measured 24*C on the iPhone 5 screen when it's idling on the app home screen. When I launch an intensive 3D game, the phone heats up and the screen registers 35*C within a few minutes.
How can we recreate an intensive background-threaded CPU process that does not consume resources and crash the app? I've tried various open-source iOS benchmark apps from GitHub but when I put them on an infinite loop in a background thread, after a few minutes the app goes out of memory and crashes.
Here's an example of one of the benchmarks I put in to the background on an infinite loop. (while 1=1). No matter what variation of benchmark code I try, the "Total Bytes" will grow without stopping until the app crashes. Click to see Instruments Screenshot.
Does anyone have examples of CPU-intensive code that I could use that would make the device get nice and hot and also prevent the app from eating resources indefinitely?
Battery drain is not an issue as we will use external battery packs connected to offset any drain.
All help appreciated!
Adam
UIApplication * application = [UIApplication sharedApplication];
if([[UIDevice currentDevice] respondsToSelector:#selector(isMultitaskingSupported)])
{
NSLog(#"Multitasking Supported");
__block UIBackgroundTaskIdentifier background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler:^ {
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
//To make the code block asynchronous
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//### background task starts
NSLog(#"Running in the background\n");
static NSString *staticString = #"some const value string to referece";
while (1 == 1)
{
NSDictionary *dictionary;
NSString *string;
NSDate *staticStart = [NSDate date];
NSLog(#"start timing");
int times = 10000000;
while (times--) {
static dispatch_once_t onceToken;
static NSDictionary *dict;
dispatch_once(&onceToken, ^{
dict = #{#"somekey":#"someotherlongvalue", #"someotherkey":#"someotherlongvalue", #"onelastkey":#"onelastvalue"};
});
dictionary = dict;
}
NSDate *staticEnd = [NSDate date];
NSLog(#"finished static dict in %f sec", [staticEnd timeIntervalSinceDate:staticStart]);
times = 10000000;
while (times--) {
dictionary = #{#"somekey":#"someotherlongvalue", #"someotherkey":#"someotherlongvalue", #"onelastkey":#"onelastvalue"};
}
NSDate *dictEnd = [NSDate date];
NSLog(#"finished dict create in %f sec", [dictEnd timeIntervalSinceDate:staticEnd]);
times = 10000000;
while (times--) {
static dispatch_once_t stringOnceToken;
static NSString *dispatchString;
dispatch_once(&stringOnceToken, ^{
dispatchString = #"someotherlongvalue";
});
string = dispatchString;
}
NSDate *staticStringEnd = [NSDate date];
NSLog(#"finished static string in %f sec", [staticStringEnd timeIntervalSinceDate:dictEnd]);
times = 10000000;
while (times--) {
string = #"someotherlongvalue";
}
NSDate *stringEnd = [NSDate date];
NSLog(#"finished string create in %f sec", [stringEnd timeIntervalSinceDate:staticStringEnd]);
times = 10000000;
while (times--) {
string = staticString;
}
NSDate *refEnd = [NSDate date];
NSLog(#"finished string reference in %f sec", [refEnd timeIntervalSinceDate:stringEnd]);
}
//#### background task ends
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});
}
else
{
NSLog(#"Multitasking Not Supported");
}
You have lots of autoreleased objects that are never given a chance to be released. Add an autorelease pool to your outer loop:
while (1 == 1) {
#autoreleasepool {
// original contents of your loop
}
}
I've been tasked with adding some instrumentation logic to an app to track the latency of various API calls. I'm struggling to come up with a clean, non side-effecting way to add timing instrumentation to methods that return RACSignal's (deferred execution API calls).
Considerations
Using ReactiveCocoa # 1.9.5 (can't upgrade at the moment)
Using Parse-RACExtensions # 0.0.2
I'd prefer to set up timings at the ViewModel layer as opposed to modifying Parse-RACExtensions. This is because the VM has extra info I'd like to log, like query parameters, and I don't need every API call instrumented.
Only record timings upon receipt of a completed event
In the spirit of painless instrumentation, the burden on the caller should be as small as is practical
Attempted Solution
The only thing I've been able to come up with is to create a concrete RACSubscriber subclass that that handles the timer logic. Besides the nasty subclass, this obviously isn't ideal as it requires an explicit subscribe:, which in turn requires a replay on the source signal. Additionally, there is a burden on the caller as they have to at least refactor to get a temporary handle to the signal.
#interface SignalTimer : RACSubscriber
#property (nonatomic) NSDate *startDate;
#end
#implementation SignalTimer
- (void)didSubscribeWithDisposable:(RACDisposable *)disposable
{
[super didSubscribeWithDisposable:disposable];
self.startDate = [NSDate date];
}
- (void)sendCompleted
{
NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:self.startDate];
NSLog(#"Time elapsed: %f", duration);
[super sendCompleted];
}
#end
Usage would look like this:
- (RACSignal*)saveFoo:(Foo*)fooParseObj {
RACSignal *save = [[fooParseObj rac_save] replay]; // Don't forget replay!
[save subscribe:[[SignalTimer alloc] initWithName#"Saving the user's foo object"]];
return save;
}
Obviously, I'm not happy with this implementation.
Final Thoughts
Ideally, I'd like a chain-able method like this, but I wasn't sure how to accomplish it/if it was possible to handle a cold signal without nasty side-effects inside a category method (like calling replay on the receiver).
[[[fooParseObj rac_save] logTimingsWithName:#"Saving the user's foo object"] subscribeNext:...];
Thoughts?
So I think I was making this way harder than it needed to be. The following category solution seems much more idiomatic, but I'd love any feedback.
#interface RACSignal (Timing)
- (instancetype)logTimingWithName:(NSString*)name;
#end
#implementation RACSignal (Timing)
- (instancetype)logTimingWithName:(NSString*)name
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSDate* startDate = [NSDate date];
return [self subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:startDate];
NSLog(#"%#: %f sec", name, duration);
[subscriber sendCompleted];
}];
}];
}
#end
I have a game where is uses a countdown timer and when the timer is up, you are brougt to a Game over view. I want to add a feature were if they tap a button, it will add like 1, 2 or 3 more seconds to the timer. I already have the code for the timer (Below), but i just need to know how to add more time to the counter. I thought of it and i have to say when the views will switch and it would need to take into a count the added time.
Code:
-(IBAction)Ready:(id)sender {
[self performSelector:#selector(TimerDelay) withObject:nil afterDelay:1.0];
[self performSelector:#selector(delay) withObject:nil afterDelay:36.5];
}
-(void)TimerDelay {
MainInt = 36;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countDownDuration)
userInfo:nil
repeats:YES];
if (timer == 0)
{
[timer invalidate];
}
}
-(void)countDownDuration {
MainInt -= 1;
seconds.text = [NSString stringWithFormat:#"%i", MainInt];
}
-(void)delay {
GameOverView1_4inch *second= [self.storyboard instantiateViewControllerWithIdentifier:#"GameOverView1"];
second.finalScore = self.currentScore;
[self presentViewController:second animated:YES completion:nil];
}
If you're using a timer to manage the game, using a perform selector at the same time (to end the game or anything else) kind of defeats the point and makes the management very complex. Choose one route and stick with it.
When the timer is running, you can change it's fire date using setFireDate:. So, you could get the current fire date, add your time to it and then set the new fire date:
- (void)extendByTime:(NSInteger)seconds
{
NSDate *newFireDate = [[self.timer fireDate] dateByAddingTimeInterval:seconds];
[self.timer setFireDate:newFireDate];
}
Then, your button callbacks are something like:
- (void)buttonOnePressed:(id)sender
{
[self extendByTime:1];
}
- (void)buttonFivePressed:(id)sender
{
[self extendByTime:5];
}
Once you've removed the performSelector which calls delay your game end will be defined by
MainInt reaching zero.
As an aside, don't do this:
if (timer == 0)
The correct approach is:
if (timer == nil)
And if the timer is nil, there's no point in trying to invalidate it...
Also a good idea to take a look at the Objective-C naming guidelines.
Based on your recent comment, it seems that you actually want the timer to continue counting at a second interval, but to add time only to the number of seconds remaining. That's even easier and doesn't require any change to the timer fire date.
- (void)extendByTime:(NSInteger)seconds {
MainInt += seconds;
seconds.text = [NSString stringWithFormat:#"%i", MainInt];
}
And you need to add a check in 'countDownDuration':
if (MainInt <= 0) {
[timer invalidate];
[self delay];
}
To determine when you're done.
You can keep reference of the time you start the timer. Then, when you want to add extra time, calculate how much time has passed since the timer started, invalidate the timer and create a new one passing as time interval the difference between the time left of the previous timer and the extra seconds.
Hy, try to use this:
Put this in -(void)viewDidLoad method
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self
selector:#selector(countDownTimer) userInfo:nil repeats:YES];
then create the -(void)countDownTimer method
- (void)countDownTimer {
// my method which returns the differences between two dates in my case
double diff = [self getDateDifference];
double days = trunc(diff / (60 * 60 * 24));
double seconds = fmod(diff, 60.0);
double minutes = fmod(trunc(diff / 60.0), 60.0);
double hours = fmodf(trunc(diff / 3600.0), 24);
if(diff > 0) {
NSString *countDownString = [NSString stringWithFormat:#"%02.0f day(s)\n%02.0f:%02.0f:%02.0f",
days, hours, minutes, seconds];
// IBOutlet label, added in .h
self.labelCountDown.text= countDownString;
} else {
// stoping the timer
[timer invalidate];
timer = nil;
// do something after countdown ...
}
}
and you can add a - (double)getDateDifference method which returns the difference between two dates in my case
- (double)getDateDifference {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSDate *dateFromString = [[NSDate alloc] init];
NSDate *now = [NSDate date];
NSString *myDateString = #"2013-10-10 10:10:10"; // my initial date with time
// if you want to use only time, than delete the
// date in myDateString and setDateFormat:#"HH:mm:ss"
// this line is not required, I used it, because I need GMT+2
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:+2]];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
// get the date
dateFromString = [dateFormatter dateFromString:myDateString];
// this line is also not required, I used it because I need GMT+2
// so I added two hours in seconds to 'now'
now = [now dateByAddingTimeInterval:60*60*2];
// getting the difference
double diff = [dateFromString timeIntervalSinceDate:now];
NSLog(#"dateString: %#", dateString);
NSLog(#"now: %#", now);
NSLog(#"targetDate: %#", dateFromString);
NSLog(#"diff: %f", diff);
return diff;
}
the output is similar to this
dateString: 2013-10-10 00:20:00
now: 2013-10-10 00:20:00 +0000
target: 2013-10-10 00:20:28 +0000
diff: 28.382786
I hope it was helpfull
Here's a library that you can use.
https://github.com/akmarinov/AMClock
I am trying to measure time taken per GET request when downloading a file using AFNetworking. I am downloading a file repeatedly in a loop.
The problem I am having is that the way I am measuring total time, it gives a much larger total time than it actually is. For example, for 50 downloads it gives 72 sec but in reality it only took around 5 sec. I also suspect 5 sec is too low for 50 downloads(the download size is 581 kb per file).
How do I effectively measure time in this case? I need time from the moment request is fired till response in received.
My method to download file:
- (void) HTTPGetRequest
{
startTime = CACurrentMediaTime(); // Start measuring time
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:http://myServer];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET"
path:#"/download/Text581KB.txt"
parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
// Save downloaded file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:#"Text581KB.txt"]];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
double elapsedTime = (CACurrentMediaTime() - startTime); // Measuring time
totalTime += elapsedTime; // Measuring total time HERE!
[results setString:[NSString stringWithFormat: #"Current Transaction Time: %f sec\nTotal Time: %f sec", elapsedTime, totalTime]];
[_resultLabel performSelectorOnMainThread:#selector(setText:) withObject:results waitUntilDone:NO];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { ((int)totalBytesExpectedToWrite));
totalDownloadSize += totalBytesExpectedToWrite;
[_DataTransferredLabel setText:[NSString stringWithFormat:#"Total Download Size: %#", [self getFileSize:totalDownloadSize/1024]]];
}];
[operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
return nil;
}];
[operationQueue addOperation:operation];
}
I am creating a NSOperationQueue in my viewDidLoad:
operationQueue = [NSOperationQueue new];
[operationQueue setMaxConcurrentOperationCount:1]; // using this as I was suspecting downloads were happening in parallel & thus 50 downloads finishing in a few secs
I am invoking the HTTPGetRequest method as follows:
- (IBAction)startDownload:(UIButton *)sender {
totalCount = [[_countText text] longLongValue]; // get # of times to download
long currentCount = 1;
completedCount = 0;
totalTime = 0;
totalDownloadSize = 0;
while (currentCount <= totalCount)
{
[self HTTPGetRequest];
[results setString:#""];
currentCount++;
}
Use AFHTTPRequestOperationLogger.
In terms of calculating cumulative time (not elapsed time), I have just created a subclass of AFHTTPRequestOperation that captures the start time. Otherwise, you won't know precisely when it started:
#interface TimedAFHTTPRequestOperation : AFHTTPRequestOperation
#property (nonatomic) CFAbsoluteTime startTime;
#end
#implementation TimedAFHTTPRequestOperation
- (void)start
{
self.startTime = CFAbsoluteTimeGetCurrent();
[super start];
}
#end
(Note I'm using CFAbsoluteTimeGetCurrent versus CACurrentMediaTime; use whatever you want, but just be consistent.)
Then in the code that's doing the downloads, you can use this TimedAFHTTPRequestOperation instead of AFHTTPRequestOperation:
TimedAFHTTPRequestOperation *operation = [[TimedAFHTTPRequestOperation alloc] initWithRequest:request];
That code's completion block can then use the startTime property of TimedAFHTTPRequestOperation to calculate the time elapsed for the given operation and add it to the total time:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
TimedAFHTTPRequestOperation *timedOperation = (id)operation;
CFTimeInterval elapsedTime = CFAbsoluteTimeGetCurrent() - timedOperation.startTime;
self.totalTime += elapsedTime; // Measuring total time HERE!
NSLog(#"finished in %.1f", elapsedTime);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
That's how you calculate the elapsedTime and append them together to calculate the totalTime.
In terms of how to know when the operations are done, I would
modify HTTPGetRequest to return a NSOperation;
have startDownload create a completion operation and then add all of these operations as dependencies:
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"finished all in cumulative time: %.1f", self.totalTime);
}];
for (NSInteger i = 0; i < totalCount; i++)
{
NSOperation *operation = [self HTTPGetRequest];
[completionOperation addDependency:operation];
}
[self.operationQueue addOperation:completionOperation];
That achieves several goals, namely creating a completion operation, calculating the total time (as opposed to the total time elapsed).
By the way, I'd also suggest pulling the creation of AFHTTPClient out of your HTTPGetRequest. You should probably only create one per app. This is especially important in case you ever started using enqueueHTTPRequestOperation instead of creating your own operation queue. I also see no need for your call to registerHTTPOperationClass.
You are incrementing the totalElapsed by elapsedTime, but elapsedTime is calculated from startTime, which itself represents the time that the jobs were first queued, not when the download actually started. Remember that HTTPGetRequest returns almost immediately (having set elapsedTime). Thus if you're queueing five downloads, I wouldn't be surprised that HTTPGetRequest runs five times (and sets and resets startTime five times) before the first request even is initiated.
The question is further complicated by the question of whether you're doing concurrent downloads, and if so, what you then mean by "total elapsed". Let's say you have two concurrent downloads, one that takes 5 seconds, another takes 7 seconds. Do you want the answer to be 7 (because they both finished in 7 seconds)? Or do you want the answer to be 12 (because they both finished in a cumulative 12 seconds)?
I'm presuming that you're looking for, in this scenario, 7 seconds, then you should set the startTime once before you initiate all of your requests, and then only calculate when all of the downloads are done. You could, for example, rather than doing any calculations in HTTPGetRequest at all, just add your own operation that is dependent upon all the other operations you added, which calculates the total elapsed. at the very end. Or, if you want the the total elapsed to just reflect the total elapsed while you're in the process of downloading, then just set totalElapsed rather than incrementing it.
Another option is to inject the "fire" date in the operation's userInfo by observing the AFNetworkingOperationDidStartNotification notification.
//AFHTTPRequestOperation *operation = [...]
id __block observer = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingOperationDidStartNotification
object:operation
queue:nil
usingBlock:^(NSNotification *note) {
operation.userInfo = #{#"fireDate": [NSDate date]};
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];