How to test with NSUserDefaults? - cocoa

In most of my classes that work with defaults I make the defaults object settable:
#property(retain) NSUserDefaults *defaults;
This is supposed to make testing easier:
// In a nearby test class:
- (void) setUp {
[super setUp];
NSUserDefaults *isolatedDefaults = [[NSUserDefaults alloc] init];
[someObjectBeingTested setDefaults:isolatedDefaults];
}
But now I have found out then when I create a fresh defaults object, there are already some values in it. Is that possible? I thought I could create an empty, isolated defaults object by calling -init. Do I have a bug somewhere in the testing code, or do I really have to do something more complex (like stubbing or mocking) if I want to test my defaults-based code?

In the end I have created a simple NSUserDefaults replacement that can be used to control the defaults environment in tests. The code is available on GitHub.

From the NSUserDefaults documentation:
init: Returns an NSUserDefaults object initialized with the defaults for the current user account.
This should normally not be empty. I am not really sure what you want to test here, since it would be a waste of time to test NSUserDefaults functionality.
But say you need some keys to be not registered yet for your test to always have the same initial point: then just remove them in setUp (and restore them later in tearDown if you want to).
Something like:
- (void) setUp {
[super setUp];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"myTestKey"];
// synchronize the change, or just use resetStandardUserDefaults:
[someObjectBeingTested setDefaults:[NSUserDefaults resetStandardUserDefaults]];
}
If you don't have a specific list of keys but need to wipe out everything, you will have to use the CoreFoundation Preferences Utilities, see CFPreferencesCopyKeyList.

We also needed to override NSUserDefaults for testing, but didn't want to change any of the application code.
So we wrote a category on NSUserDefaults that allows us to override values returned by objectForKey: at runtime using method swizzling.
It looks like this in Objective C:
NSLog(#"Value before: %d", [[NSUserDefaults standardUserDefaults] boolForKey:#"Example"]);
// Value before: 0
[[NSUserDefaults standardUserDefaults] overrideValue:#(YES) forKey:#"Example"];
NSLog(#"Value after: %d", [[NSUserDefaults standardUserDefaults] boolForKey:#"Example"]);
// Value after: 1
And like this in Swift:
print(UserDefaults.standard.bool(forKey: "ExampleKey")) // false
UserDefaults.standard.overrideValue(true, forKey: "ExampleKey")
print(UserDefaults.standard.bool(forKey: "ExampleKey")) // true
You can find our code on Github: https://github.com/jakob/NSUserDefaultsOverride

Related

Is it possible to use bindings in the nib with ScreenSaverDefaults?

I'm working on a screensaver, so I'm supposed to use ScreenSaverDefaults instead of NSUserDefaults. I'd like to have my configure panel use bindings for its UI, but they need to be wired to ScreenSaverDefaults, and I can't see a way to do that; the only defaults controller available in IB (xCode) is the standard user defaults controller. Is there a workaround, or is it just not possible to use bindings in the nib in the context of a screensaver?
I have successfully used bindings in a screen saver.
(Possibly important note: I'm still developing on 10.6, with XC 3.2.6; this code has only been lightly tested so far on 10.10 (but does seem to work). Also, I mix C++ and Objective-C, so you may have minor code cleanup to do, in what follows.)
My code creates, and the UI binds to, a custom user defaults controller (controls bind to File's Owner, which is set to my screen saver view, the controller key is empty, and the model key path is self.defaultsController.values.key_val, where key_val is whatever key is used in the defaults plist to access the value bound to a control).
You must also create your own screen saver defaults, and direct your custom user defaults controller to use them, early in screen saver initialization (such as in the initWithFrame: method of your screen saver view), like so:
// Find our bundle:
NSBundle * screenSaverBundle = [NSBundle bundleForClass: [self class]];
// Load per-user screen saver defaults:
ScreenSaverDefaults * screenSaverDefaults = [ScreenSaverDefaults defaultsForModuleWithName: [screenSaverBundle bundleIdentifier]];
// Create default defaults (values to use when no other values have been established):
NSDictionary * defaultDefaultValues = [self defaultDefaults];
// Register default defaults as fallback values (in registration domain):
[screenSaverDefaults registerDefaults: defaultDefaultValues];
// Configure custom user defaults controller to use the correct defaults:
// NOTE: defaultsController of NSUserDefaultsController * type *MUST* be declared using #property in your header, and accessors must be provided (typically using #synthesize in the implementation section).
defaultsController = [[NSUserDefaultsController alloc] initWithDefaults: screenSaverDefaults initialValues: nil]; // <- change nil to factory defaults, if desired
// Make sure changes are only saved when committed by user:
[defaultsController setAppliesImmediately: false];
(The above code was slightly rearranged from various methods in my own code; might have a typo or two, but the gist is correct.)
An implementation of defaultDefaults looks something like this (pardon my unconventional style):
- (NSDictionary *) defaultDefaults
{
NSString * ResourcesPath = [[self screenSaverBundle] resourcePath];
NSString * DefaultDefaultsPath = [ResourcesPath stringByAppendingPathComponent: #"DefaultDefaults.plist"];
NSDictionary * DefaultDefaults = [NSDictionary dictionaryWithContentsOfFile: DefaultDefaultsPath];
return DefaultDefaults;
}
If you want to provide a "factory reset" capability in your screen saver, you'll need to set the initialValues: argument to an "original factory values" dictionary when creating the custom user defaults controller. In response to a "Reset to Factory Defaults" button, simply send revertToInitialValues: to the controller.
Finally, please note that depending on the intended lifetime of some of the objects created in the above code, you may need to retain some of them (and release them properly later). I assume you understand this bhaller; I'm just pointing this out for the general audience.
Swift Version:
First, register default datas.
let defaults = ScreenSaverDefaults.init(forModuleWithName: "com.your.xxxx" ?? "")!
defaults.register(defaults: ["isShowSecond":true,
"userThemeIndex":0,
"userClockIndex":0
])
You can set value:
defaults.set(false, forKey: "isShowSecond")
You can get value:
let defaults = ScreenSaverDefaults.init(forModuleWithName: "com.your.xxxx" ?? "")!
let userClockIndex = defaults.bool(forKey: "isShowSecond")

How to avoid copy and pasting?

I'd like to improve this method if possible: this is a small section whereby all of the textfield (eyepiece, objectivelenses etc) texts are saved. Unfortunately, having to do this lots of times for each part of my app is prone to error so I would like to improve it. I'm thinking some sort of fast enumeration with arguments for the method being the textfields etc. and I can have all the keys in a dictionary (which is already set up). Just a pointer to the right docs or, perhaps, some sort of process that has worked for you would be fantastic!
-(IBAction)saveUserEntries {
if (eyepiece.text != nil) {
eyepieceString = [[NSString alloc] initWithFormat:eyepiece.text];
[eyepiece setText:eyepieceString];
NSUserDefaults *eyepieceDefault = [NSUserDefaults standardUserDefaults];
[eyepieceDefault setObject:eyepieceString forKey:#"eyepieceKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"eyepieceKey"];
}
if (objectiveLenses.text != nil) {
objectiveLensString = [[NSString alloc] initWithFormat:objectiveLenses.text];
[objectiveLenses setText:objectiveLensString];
NSUserDefaults *objectiveDefault = [NSUserDefaults standardUserDefaults];
[objectiveDefault setObject:objectiveLensString forKey:#"objectiveKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"objectiveKey"];
}
Thank you for taking the time to read this!
I will attempt to answer this question based on a OOP solution.
Create a method that accepts whatever type object these textboxes are as an argument, send the reference of said object to the method, and save the entry in a similar method you do know. This will avoid the "copy and paste" errors you are worried about.
You should be able to loop through every instance of said object that exists, if a cocoa application, works like similar to Java and .NET ( I really don't know ). I just know there must be a way to loop through every instance of a single object within the application domain.
If this was .NET I simply would suggest TextBox.Name and TextBox.String to make this a generic method that could be used to save the properties of any TextBox sent to it. If this doesn't anwer your question ( was a little long for a comment ) then I aplogize.

NSUserDefaults and registerDefault, a standard behavior?

I need to store a numeric parameter in NSUserDefault, this parameter must be as default 1
So i write this code :
NSNumber *one = [NSNumber numberWithInt:100];
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
one,#"my-param",
nil];
[def registerDefaults:dict];
This parameter has a bind over a checkbox in IB so i can check that it's correctly set and when i start my app i see checkbox with state "on".
On the other side i must check this value programmatically so... i do something like :
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger my_param = [defaults integerForKey:#"my-param"];
I excepted that since the value is not set by the user this function return value that i set as default (in my case "1") but with my surprise i found that if value has never been set by user it returns 0 ... as you can understand this's terrible :P because now i can't understand if this "0" is obtained by user choice of this's a consequence of a non-set value... how can i write code to manage this situation ?
To store the numeric values in NSUserDefaults you can simply directly use as follows:
To set the integer Value in NSUserDefault use as follows below:
NSInteger lInteger = 10;
[[NSUserDefaults standardUserDefaults] setInteger:lInteger forKey:#"integerkey"];
[[NSUserDefaults standardUserDefaults] synchronize];
And to use that NSInteger value anywhere in your project use as below:
NSInteger linteger = [[NSUserDefaults standardUserDefaults] integerForKey:#"integerkey"];
And i didnt understand your question exactly, but anyhow i think this above code may help you.

Core Data fails to generate primitive accessors

From my understanding of Core Data, all that is necessary for primitive accessors to work is the #dynamic directive for the property name (as well as declaring primitive accessors for that property within the entity implementation).
For some reason, when using the generated primitive accessor the setState: method is not modifying the state property:
- (int)state
{
NSNumber * tmpValue;
[self willAccessValueForKey:#"state"];
tmpValue = [self primitiveState];
[self didAccessValueForKey:#"state"];
return [tmpValue intValue];
}
- (void)setState:(int)value
{
[self willChangeValueForKey:#"state"];
[self setPrimitiveState:[NSNumber numberWithInt:value]];
[self didChangeValueForKey:#"state"];
}
while using the key-value-coding version does modify the state property
- (int)state
{
NSNumber * tmpValue;
[self willAccessValueForKey:#"state"];
tmpValue = [self primitiveValueForKey:#"state"];
[self didAccessValueForKey:#"state"];
return [tmpValue intValue];
}
- (void)setState:(int)value
{
[self willChangeValueForKey:#"state"];
[self setPrimitiveValue:[NSNumber numberWithInt:value] forKey:#"state"];
[self didChangeValueForKey:#"state"];
}
in both cases, I primitive accessors are declared as follows (and as per Apple's example and code generation):
#interface Post (CoreDataGeneratedPrimitiveAccessors)
- (NSNumber *)primitiveState;
- (void)setPrimitiveState:(NSNumber *)value;
#end
I'm a bit at a loss to why this would be. Any help would be greatly appreciated!
After tremendous amounts of head-scratching, debugging, fiddling and guess-and-check, I've finally figured out what the problem is: Core Data primitive accessors AREN'T dynamically generated if you define those attributes as instance variables. I had defined them for debugging purposes (as GBD cannot see the values of properties without defined ivars, it seems), and this prevented primitive accessors from being generated correctly. This is something that Apple should really document in some form. As it's very difficult to discover on one's own. I hope this helps others who've been having the same issue!
I've been looking into this and one of the things discovered is that, contrary to docs, the implementation file generated from the data model does NOT list the primitive dynamic accessors. Other places state that you have to add them yourself. Could that be the issue?
Are you using/modifying the code of an NSManagedObject generated by Xcode? I believe that by default these are generated as "commented" out by an #if 0 directive.
Just wanted to say that I am having the same problem and had to switch to setPrimitiveValue and primitiveValueForKey based on your comment here. It bothers me that the default implementation does not work. Of note in my case is that I am subclassing another NSManagedObject. Not sure if that's your case as well.

componentsJoinedByString gives me EXC_BAD_ACCESS

I have an NSMutableArray i am trying to convert into a string.
Declaring my NSMutableArray...
NSMutableArray *listData;
And later inside a method...
NSString *foo = [listData componentsJoinedByString:#"|"];
NSLog(#"%#",foo);
It seems no matter what i try i keep getting EXC_BAD_ACCESS.
To make sure each element in my array was an NSString i also tried this...
NSMutableArray *mArray = [[NSMutableArray alloc] init];
for (id ln in listData) {
NSString *boo = [NSString stringWithFormat: #"%#",ln];
[mArray addObject:boo];
}
NSString *foo = [mArray componentsJoinedByString:#"|"];
NSLog(#"%#",foo);
I can manipulate my NSMutableArray by adding/deleting objects in the same method or other methods inside my class. But when i try "componentsJoinedByString" the error pops up. Does anyone have any advice or another way i can combine this array into a single NSString?
In the code you've given, there will never be an NSMutableArray for listData. At some point in your code, you'll need to create one, and presumably populate it.
Edit
Okay, so you may get into memory management problems here, so let's be a bit clearer:
You're synthesizing getters and setters for the instance variable, so it's good practice to use those to access it, they'll take care of retain and releasing appropriately.
To set listData you can simply use
self.listData = [listManage getList:[[NSUserDefaults standardUserDefaults] stringForKey:#"list_name"] list:#"LIST"];
or
[self setListData:[listManage getList:[[NSUserDefaults standardUserDefaults] stringForKey:#"list_name"] list:#"LIST"]];
if you prefer.

Resources