NSUserDefaults setObject:forKey freezes - objective-c

I am using NSUserDefaults to store NSNumber value.
- (void)saveValue {
if(_key != nil) {
if(_value != nil) {
NSDate *date = [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:_value forKey:_key];
NSTimeInterval t = -date.timeIntervalSinceNow;
NSLog(#"%s - %lf", __PRETTY_FUNCTION__, t);
} else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:_key];
}
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
(NSLog was added for test purposes, to show how long the operation takes)
But sometimes it takes seconds:
2019-12-16 16:10:37.450025+0200 MyProduct[23565:262671] -[CachedDefaultsValue saveValue] - 0.017004
2019-12-16 16:10:48.108883+0200 MyProduct[23565:262671] -[CachedDefaultsValue saveValue] - 6.022574
2019-12-16 16:10:57.670287+0200 MyProduct[23565:262671] -[CachedDefaultsValue saveValue] - 0.000176
2019-12-16 16:11:04.381613+0200 MyProduct[23565:262671] -[CachedDefaultsValue saveValue] - 0.000142
2019-12-16 16:11:20.034960+0200 MyProduct[23565:262671] -[CachedDefaultsValue saveValue] - 3.018475
2019-12-16 16:11:25.661984+0200 MyProduct[23565:262671] -[CachedDefaultsValue saveValue] - 0.000185
2019-12-16 16:11:40.863433+0200 MyProduct[23565:262671] -[CachedDefaultsValue saveValue] - 3.017213
I was surprised that -[NSUserDefaults setObject:forKey:] takes so long. Up to 6 seconds as you can see in the log above.
I followed the suggestion and removed all the -[NSUserDefaults synchronize] methods.
synchronize Waits for any pending asynchronous updates to the defaults
database and returns; this method is unnecessary and shouldn't be
used.
2019-12-16 16:36:16.827763+0200 MyProduct[30538:298345] -[CachedDefaultsValue saveValue] - 0.018135
2019-12-16 16:36:27.496848+0200 MyProduct[30538:298345] -[CachedDefaultsValue saveValue] - 3.017637
2019-12-16 16:36:37.759195+0200 MyProduct[30538:298345] -[CachedDefaultsValue saveValue] - 1.014380
2019-12-16 16:36:47.239685+0200 MyProduct[30538:298345] -[CachedDefaultsValue saveValue] - 0.000139
2019-12-16 16:36:52.877213+0200 MyProduct[30538:298345] -[CachedDefaultsValue saveValue] - 0.000140
2019-12-16 16:37:03.623115+0200 MyProduct[30538:298345] -[CachedDefaultsValue saveValue] - 0.000298
2019-12-16 16:37:19.192752+0200 MyProduct[30538:298345] -[CachedDefaultsValue saveValue] - 4.013809
But in any way, I got 3 and 4 seconds freezes.
macOS: 10.14.5 (18F132)
XCode Version 11.2.1 (11B500)
Does anyone have any idea how the freeze can be solved?
Or some ways to research the issue...

Related

Objective-C, scheduledTimerWithTimeInterval Progressive Increase

I have a determinate progress bar which looks like the following:
NSInteger progressValue;
NSTimer *timerObject;
- (void)incrementProgressBar {
// Increment the progress bar value by 1
[progressBar incrementBy:1.0];
progressValue++;
[progressBar setDoubleValue:progressValue];
}
And then I call it by:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(incrementProgressBar) userInfo:nil repeats:YES];
My question: Is it possible to increment the scheduledTimerWithTimeInterval for each iteration?
So for example if the progressValue was 1 then scheduledTimerWithTimeInterval would be 1, but if progressValue was 2 then scheduledTimerWithTimeInterval would be 2, etc.
Basically what I'm trying to do is progressively slow the progress bar down the closer it gets to 100. There's probably some better way or recommendation for doing this, but it's essentially what I'm looking for, thanks.
Use -fireDate:(NSDate*) to set the next date when the NSTimer will fire. For further explanation of the API, go here.
For example, you can achieve that as follows:
NSDate *currentDate = [NSDate date];
NSDate *newDate = [NSDate dateWithTimeInterval:(NSTimeInterval)progressValue sinceDate: currentDate];
timerObject.fireDate = newDate;

Can't set NSSlider value in applicationDidFinishLaunching

I need to set a slider in preferences window of a cocoa application.
If I set the NSSlider in awakeFromNib like so
-(void)awakeFromNib{
[thresholdSlider setInValue:9];
}
the preference window updates with the value when opened.
Though, since it is a preference window, I need to register the Value with NSUserDefault, so when the application at launch would run :
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
[thresholdSlider setValue:[NSUserDefaults standardUserDefaults] forKey:kthresh];
NSLog( #"%#",[thresholdSlider objectValue]);
}
But I cant even set the slider value in the applicationDidFinishLaunching method
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
[thresholdSlider setIntValue:9];
NSLog( #ā€œ%dā€,[thresholdSlider intValue]);}
returns 0 and slider is set to minimum value(set in IB) in the preferences window.
Where can I call the [thresholdSlider setValue:[NSUserDefaults standardUserDefaults] forKey:kthresh]; to get the slider updated with the user value on last application quit?
The code edited according to Vadian proposition :
+(void)initialize{
NSDictionary *dicDefault = #{#"kthresh":#9};
[[NSUserDefaults standardUserDefaults]registerDefaults:dicDefault];}`
`- (void)applicationDidFinishLaunching:(NSNotification*)aNotification{
`//Preferences
NSInteger thresholdValue = [[NSUserDefaults standardUserDefaults] integerForKey:#"kthresh"];`
thresholdSlider.integerValue = thresholdValue;}`
`-(void)applicationWillTerminate:(NSNotification *)notification {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:thresholdSlider.integerValue forKey:#"kthresh"];
[defaults synchronize];}`
In AppDelegate as soon as possible register the key value pair with a default value. This value is always used if no custom value has been written to disk yet.
NSDictionary *defaultValues = #{#"kthresh" : #9};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues];
Then set the value of the slider wherever you want, consider the syntax
NSInteger thresholdValue = [[NSUserDefaults standardUserDefaults] integerForKey:#"kthresh"];
thresholdSlider.integerValue = thresholdValue;
To write the value to disk use this
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults] ;
[defaults setInteger:thresholdSlider.integerValue forKey:#"kthresh"];
[defaults synchronize];
Do not use setValue:forKey: and valueForKey: to talk to NSUserDefaults
Alternatively use Cocoa bindings and bind the key integerValue to the NSUserDefaultsController instance in Interface Builder.
[thresholdSlider setValue:[NSUserDefaults standardUserDefaults] forKey:kthresh];
should be
[thresholdSlider setObjectValue:[[NSUserDefaults standardUserDefaults] valueForKey:kthresh]];

NSTimer - Why does scheduledTimerWithTimeInterval work, yet initWithFireDate doesn't?

This calls my selector repeatedly each 60 seconds as desired:
autoDeleteTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:[SimpleDB class] selector:#selector(autoDelete:) userInfo:nil repeats:YES];
This next line doesn't call it at all. Not initially nor after 60 seconds:
autoDeleteTimer = [[NSTimer alloc] initWithFireDate: [NSDate dateWithTimeIntervalSinceNow:1] interval:60 target:[SimpleDB class] selector:#selector(autoDelete:) userInfo:nil repeats:YES];
Can anyone explain why? Thanks.
You need to add the second timer to the main loop:
[[NSRunLoop mainRunLoop] addTimer: autoDeleteTimer forMode:NSDefaultRunLoopMode];
From the documentation of the method:
- (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
Return Value:
The receiver, initialized such that, when added to a run loop, it will
fire at date and then, if repeats is YES, every seconds after that.
You must add the new timer to a run loop, using addTimer:forMode:.
Upon firing, the timer sends the message aSelector to target. (If the
timer is configured to repeat, there is no need to subsequently re-add
the timer to the run loop.)
NSTimer Apple Doc

UIDatePicker for time returns very strange results

I've searched the forum and there are quite a few posts on strange behavior of the UIDatePicker, but none of the solutions solved my problem.
I have a simple UIDatePicker initialized like this:
self.picker = [[UIDatePicker alloc] init];
self.picker.datePickerMode = UIDatePickerModeTime;
self.picker.calendar = [NSCalendar currentCalendar];
self.picker.timeZone = [NSTimeZone localTimeZone];
self.picker.date = self.initialDate;
[self.view addSubview:self.picker];
This is done in the viewDidLoad method of a simple view (no xib, no interface builder, just plain code).
Now I have a button, which retrieves the date and "processes" it.
NSDate* selected = self.picker.date;
NSLog(#"%#", selected);
NSDateComponents* components = [self.picker.calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:selected];
NSLog(#"0001-01-01 %02i:%02i:%02i +0000", components.hour, components.minute, components.second);
[components setSecond:0];
NSLog(#"0001-01-01 %02i:%02i:%02i +0000", components.hour, components.minute, components.second);
selected = [self.picker.calendar dateFromComponents:components];
NSLog(#"%#", selected);
Since the date picker will not allow the user to set seconds, I just want to reset them to zero (because I need to compare dates later).
When I select "16:00" (which is 4:00 pm) The log output is:
2012-05-21 17:43:48.428 Green Thumb[26828:fb03] 0001-01-01 14:55:36 +0000
2012-05-21 17:43:48.428 Green Thumb[26828:fb03] 0001-01-01 16:00:56 +0000
2012-05-21 17:43:48.429 Green Thumb[26828:fb03] 0001-01-01 16:00:00 +0000
2012-05-21 17:43:48.429 Green Thumb[26828:fb03] 0001-01-01 14:54:40 +0000
So the original date taken from the picker is completely off. The time taken from the components (which is created based on the date from the picker) is correct, as is the corrected time. But when I put the components back in a NSDate instance, it all gets messed up again (but differently).
Me and google we are both out of ideas, so I hope we can create some new ones here!
Thanks in advance, LetzFlow
PS: I'm testing this with the iPhone 5.1 Simulator.
EDIT #1:
I've tracked down the actual problem, but I'm stuck again:
I've tracked it down to the following point: I'm storing the dates in the NSUserDefaults object.
NSUserDefaults defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:[NSNumber numberWithFloat:[time timeIntervalSince1970]] forKey:#"notification.time"];
And here I get them out again:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber num = [defaults valueForKey:#"notification.time"];
NSDate* time = [NSDate dateWithTimeIntervalSince1970:[num floatValue]];
Interesting enough: What goes in, is not what comes out! But why? :)
EDIT #2:
And last but not least I've solved it myself. Thankfully, because it's the most stupidest mistake ever.
NSTimeInterval is not a float but a double. And while up until now I never ran into any problems with using a float saving it in the NSUserDefaults seems to have brought forward the errors.
Your NSLog statements seem to have the wrong format (i.e. the "0001-01-01" part). Try this instead:
NSString *output = [NSDateFormatter localizedStringFromDate:selected
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterShortStyle];
NSLog(#"%#", output);

Is it possible to save the state of a timer in the User Defaults?

I have a label on which I am showing countdown timer.
Now if I close my app the timer will be off and the label's text also. I know that we can save the label's text value. But how do we show the correct countdown when the app starts again.
Suppose I close at 00:05:35 after 3 minutes when app is launched again the label should show 00:02:35 and the timer should be there for remaining countdown
Yes, simply store the time at which your app was closed and the time left to count down in NSUserDefaults. When the app starts again you get the time it was closed from NSUserDefaults and the time left. Using the current time it's simple math calculating the corrected time left on your count down.
Something like this might do the trick, untested of course:
// save state
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDate *now = [NSDate date];
double countDown = 45.0; // in seconds, get this from your counter
[userDefaults setObject:now forKey:#"timeAtQuit"];
[userDefaults setDouble:countDown forKey:#"countDown"];
[userDefaults synchronize];
// restore state
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDate *timeAtQuit = [userDefaults objectForKey:#"timeAtQuit"];
double timeSinceQuit = [timeAtQuit timeIntervalSinceNow];
double countDown = timeSinceQuit + [userDefaults doubleForKey:#"countDown"];
Or you could just calculate the date/time (NSDate) you want it to expire and save that in your defaults. On relaunch compare against that date to know if it has expired or if you need to set a timer to catch the future expiration.