I read the question Easy way to see saved NSUserDefaults?
I found the .plist file for the app, but where's the one for the testing bundle?
If you use Xcode 4's integration with OCUnit, it injects the test bundle into a running application. NSUserDefaults are therefore saved in the application itself. This is problematic for unit tests: When you run the app manually, your defaults may have been overwritten by your tests.
Instead of using [NSUserDefaults standardUserDefaults] directly, use dependency injection, passing in user defaults as an initializer argument. Then you can pass in a test double that you can query. (An alternative to dependency injection is returning NSUserDefaults from a helper method, overriding that method to return the test double when necessary.)
In my environment (Xcode 4.2, running tests in the simulator) NSUserDefaults writes to the following file:
~/Library/Application Support/iPhone Simulator/Library/Preferences/otest.plist`
I guess you didn't see the file because of NSUserDefaults's caching mechanism (explained in the class reference), and because your project's unit tests do not run long enough for the caching time interval to elapse.
otest.plist is guaranteed to be written, though, if something invokes the NSUserDefault instance method synchronize while the tests are running.
It seems that OCUnit doesn't store NSUserDefaults persistently but rather in memory. But, I could be wrong.
Related
Documentation is not clear on how to use NSUbiquitousKeyValueStore with edge cases.
If I want to set a value, I understand that I should set a value to both NSUserDefaults and NSUbiquitousKeyValueStore since iCloud could be disabled. However in my tests [NSUbiquitousKeyValueStore defaultStore] return a valid object even if iCloud is disabled (tested on Mac OS).
Also, to my understanding is that if iCloud is enabled, NSUbiquitousKeyValueStore's values are stored to disk (and available offline). What are the reason to use NSUserDefaults if you are sure that you have less than 64KB of data?
I am using
http://blog.mugunthkumar.com/coding/ios-code-mkicloudsync-sync-your-nsuserdefaults-to-icloud-with-a-single-line-of-code/
It is a simple class written by Mugunth Kumar (thanx !) that does the work for you... Unless you have special needs, add one line of code and it will do all the reading and writing to iCloud... all you need to do is read and write to NSUserDefaults as usual...
Edit:
Carful, if you remove an item from NSUserDefaults the code I linked to above will not remove the item from the cloud. Whenever you remove an item from NSUSerDefaults please do the same to NSUbiquitousKeyValueStore as So:
NSUbiquitousKeyValueStore* keyValueStore=[NSUbiquitousKeyValueStore defaultStore];
[keyValueStore removeObjectForKey: yourKey];
The Mugunth Kumar answer provided above works beautifully if you want to sync all of your NSUserDefaults!
However, it is an ALL or NOTHING approach. You can not pick or choose the defaults you wish to sync.
I found this Tutorial that may be of assistance if you are looking to be more picky.
I wrote a simple iOS category that can be used to save a value also into NSUbiquitousKeyValueStore while storing into NSUserDefalt.
hope this help:
https://github.com/RiccardoPaolillo/NSUserDefault-iCloud
I've trawled through questions here on SO looking for any hints to why I'm seeing this behaviour, and nothing yet.
Consider a class (actually two classes exhibiting the same problem), built into a static library, wrapped in a framework bundle (steps used). They inherit from Foundation framework class clusters (NSMutableDictionary and NSMutableArray).
The use of these classes relies on some static variables being initialised before a static function (not class method!) is used to allocate and initialise an instance (a kind of factory helper function I guess?).
When an iOS app project links to that framework there is a difference the Objective-C runtime class loading behaviour between the Simulator and the Device.
Specifically, on a device (iPhone 4, iOS 4.3.3) when the app is loaded these classes do not get a +load message, and the static vars do not initialize, therefore the static factory method fails. On the Simulator, the messages are sent, and all works as intended. Could it be a problem with the Device runtime having a
My question is, can my framework be configured differently to ensure the +load messages are sent? Or have I run into a bug with static library/framework class loading in iOS?
The classes are from the JSONKit library (JKArray, JKDictionary).
An example project that illustrates this problem is here – https://github.com/ohhorob/JSONKit-in-framework-demo
EDIT: As per #bbum's suggestion, I've verified that the JKDictionary and JKArray classes are in fact loaded and available while the application is running. The DeviceBroken branch on the GitHub project is updated with the verification used.
I filed a bugreport (#9461567) with Apple.
The +load methods are not called because you did not actually create a static library but a Relocatable Object File bundle. If you create the static framework with either make-fmwk or the iOS Universal Framework template then the load methods will be called as expected.
Odd; I'd do an NSLog(#"klassy klass %#", [MysteryClass class]); and make sure the classes are actually loaded (but see below -- this may "fix" the problem).
If they are, then this is a bug in the DYLD loader and please file it.
If not, then it is likely that the linker is stripping the class(es) because nothing references them directly. Try adding [MysteryClass class] in the app's applicationDidFinishLaunching: method (doesn't really matter where or, even, if it gets executed... but that'll be an obvious spot).
Also, I'd suggest not using +load and, instead, writing a constructor function. I.e.:
__attribute__((constructor))
static void initLibrary()
{
....
}
If this is a linker issue, that may or may not fix the problem. It is, however, much clearer as to your intentions than the rather magical +load method.
I'm writing a unit test with GHUnit. I would like to check if a file is being generated in the Caches directory within the Library directory of another app. Is this feasible, or are apps prevented from accessing each others' Library directories? And if so, are there any possible workarounds for creating a unit test for this? Could I possibly store the generated file in a different directory, such as Documents, or would that not help?
On device it is not possible at all. In simulator you can theoretically do it if you know the random UUID that names the directory that the application is installed in.
But I would highly discourage you from even trying. Instead make the code you intend to test stand enough of it's own to be testable. A unit test that verifies if another application behaves is on it's own almost as useless as a unit test that verifies that you call an API with the correct arguments.
I don't understand the context that you're attempting to unit test, so maybe my answer is wrong, but what I get is that you want to access your other app because GHUnit builds as a separate app.
There should be no reason to do this - instead of beating the sandbox problem, let's go around the whole issue.
Why not run whatever cache-creating code you are trying to test in the first place from the test target app, and then check the Libary/Cache directory of your bundle?
That's all there should be to it.
Apps can only access their own folders within their sandbox. Is your unit testing app separate from your app that you want to verify the cache contents of?
No, what you would need to do is use url prefixes and have the methods to do whatever you need done registered by that application on install.
I have an iPhone app that stores some settings using NSUserDefault standardUserDefaults.
When I add new features to the app I need to add new default settings and so I have to migrate/upgrade the NSUserDefaults. For now, I just store the version number and check this when the app is launched. It has quickly become very messy as I have to add lots of if statements. I cannot assume that the user is just upgrading from the previous version but perhaps even a couple of versions before.
I like the way that CoreData seems to handle migrating table changes but I want to provide 2.2.1 SDK compatibility and of course CoreData is not the same thing as NSUserDefaults.
Any suggestions or best practices?
Hmm… I wouldn't "upgrade" the user defaults in this way, but instead query NSUserDefaults as usual, only if the default isn't set (objectForKey will return nil in that case), use a default value (which may then be written back to the user defaults to save time the next time). Since you'll have to do this every time a "new" default (i.e. one that didn't exist in 1.0) is read, I advise doing a wrapper class/file that does this, this way the code is written only once for each "new" default.
Also, while the question/problem is different, this answer works just as well in your case.
For QA purposes I display our app's build version in the application' settings view as a PSTitleValueSpecifier. I set our app's current build number as the DefaultValue and I update it for every new build.
My problem is, whenever I deploy a new build of our application on a dev phone via Xcode, the version value doesn't update but remains with the previous build. The only way to update the version's value is to delete the app from the dev phone and then deploy the new build. Obviously, this approach does not please our QA department since they then loose all of their persisted data.
I have tried, cleaning the project, re-compiling and deploying but I get the same behavior.
Has anyone else experienced this issue? Any idea on how to work around it/fix it?
It is because this value is stored using NSUserDefaults which is stored on the disk and only removed when the app is removed. You can explicitly set it in code the same way you would any other user default. You could just create a field in your Info.plist that you change when you want to send a new build. Read that value from the plist on startup and then write it to your user defaults with:
NSString *value = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"build_version"];
[[NSUserDefaults standardDefaults] setObject:value forKey#"build_version"];
[[NSUserDefaults standardDefaults] synchronize];
Of course, you replace "build_version" with whatever your key name is in your settings file. It's a hack, but it might satisfy your QA people.
For argument sake, it is actually reasonable to expect that you have to completely remove the previous version before seeing the version number update, however I realize not all QA people are reasonable. ;-)