Today I encountered a rather confusing Problem:
When my app starts I'm downloading some things and after parsing the responses I write the data to the NSUserDefaults.
Now this was working fine until I recently discovered that sometimes my entire UI freezes and wont unfreeze until I relaunch the app. When I hit pause programm execution in the debugger I get:
The documentation says that NSUserDefaults is threadsafe and from what I can see no other threads are trying to access the user defaults...
Any Ideas on how to resolve/further debug the issue would be greatly appreciated!
Thanks in advance!
Code that's causing the problem:
This is called after I receive a response the webservice:
+ (void)updateWithData:(NSData *)data
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSError *error;
NSArray *decodedData;
if (data) {
decodedData = [NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable
format:NULL
error:&error];
}
if (!error && decodedData) {
[userDefaults setObject:decodedData forKey:kUserDataUserDefaultsKey];
}
else{
NSLog(#"%#", error.localizedDescription);
}
}
Updated with new code sample:
You should convert the decodedData into archived data in order to store it in NSUserDefaults:
if (!error && decodedData) {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject: decodedData]];
[userDefaults setObject: archivedData forKey:kUserDataUserDefaultsKey];
}
And retrieve the data with this:
NSArray *storedData = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:kUserDataUserDefaultsKey]];
Related
I have an iOS app, need to handle user share data from third app.
To handle this, I cache the data by UserDefaults in extension module:
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.app.share"];
// write data
[userDefaults setValue:[((NSURL*) item) absoluteString] forKey:#"shareData"];
//open app
[self openApp];
After that, app is opened and then can read and handle the data:
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.app.share"];
//read data
NSString *data = [userDefaults valueForKey:#"shareData"];
NSLog(#"%#", data);
Till now, everything is ok. App host can get the share data from extension correctly.
However, when I need to remove the data after handling:
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.app.share"];
//read data
NSString *data = [userDefaults valueForKey:#"shareData"];
NSLog(#"%#", data);
...use the data and then remove it
[userDefaults removeObjectForKey: #"shareData"]
Then strange thing happens as following steps:
share data from third app.
if app no running , then after open, data is nil.
if app is running, then the app switch to get the data correctly.
That is to say, the data is missing during app launching.
What's the reason then?
Find a way. Add a timer or delay, when time is out, remove the key in extension module. It works.
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.app.share"];
//write data
[userDefaults setObject:[((NSURL*) item) absoluteString] forKey:#"shareData"];
[self openApp];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[userDefaults removeObjectForKey:#"shareData"];
});
I am doing an iphone game and saving data using NSUserDefaults. Here is how I save a user:
-(void) saveUser:(User*)user forPosition:(int) position{
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:user];
NSUserDefaults*savedData=[NSUserDefaults standardUserDefaults];
[savedData setValue:myEncodedObject forKey:[NSString stringWithFormat:#"user%i",position]];
[savedData synchronize];
self.currentUser=user;}
And here is how I load it:
-(BOOL) loadUser:(int) position{
if([savedData objectForKey:[NSString stringWithFormat:#"user%i",position]]){
NSData *encodedObject = [savedData objectForKey:[NSString stringWithFormat:#"user%i",position]];
User *user = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
self.currentUser=user;
return YES;
}
else return NO;}
As you can see, I am saving a custom class on NSUserDefaults. To do that I used guidance from How to store custom objects in NSUserDefaults (in case anyone is interested).
When I use this in the simulator it works perfectly. The problem is that if I run this on my iphone (iphone 6 on ios 8.1) it doesn't load anything.
Any ideas of what it could be? I've already visited this question NSUserdefaults works on Simulator but not on Device but nothing worked.
Thanks!
I guess there could be some problem saving it.
Check what exactly is being saved.
You can print the complete NSUserDefaults using this
Have you tried setObject instead of setValue ?
[savedData setObject:myEncodedObject forKey:[NSString stringWithFormat:#"user%i",position]];
I am requesting a file representation of an NSDictionary from Parse.com (yes I know there are other more obvious ways to store dictionaries) and then in the completion block the data gets unarchived and assigned to a property.
When this code executes it never seems to return from the unarchive line. Nothing freezes, there is no error or exception. The app continues running as though everything is fine. If I set break points at the unarchiver line and the line after it, the first breakpoint gets hit but the second never does.
I can confirm that the function has returned the expected amount of data for this file.
PFFile *definitionFile = appObject[#"myFile"]; //PFFile reference from a PFObject
[definitionFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
if (data) {
//A breakpoint on the following line does fire and show that there is data being given to the unarchiver
self.pendingDefinition = (NSDictionary *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
//////Nothing beyond this point gets executed////////
[self handleNewDefinition];
} else {
NSLog(#"ERROR");
}
}];
As it turned out the data was being stored as a plist and I needed to use plist serialization to extract it:
if (data) {
NSError *error;
NSPropertyListFormat format;
NSDictionary* plist = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:&format error:&error];
if(!plist){
NSLog(#"Error: %#",error);
}
I am pasring json data from a url and taking one of teh values to NSUserdefaults to use it it application view .
Example user will enter an unique code given to him and this code will be appended to the requested URL , accoridng to the requested code the json value will be changed .
At present when i enter code it is fetching and saving NSuserdefaults and passing it to the label filed in view . But when i enter new code and fecth new data it is not updating in the view . If i restart the application and enter new code then it is showing new Value . Can somebody help me . here is the code
NSString *jsonUrlString = [[NSString alloc] initWithFormat:#"http://mywebsite.com/json/code.php?user=%#",_opcodeField.text];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
[_indicator startAnimating];
_indicator.hidesWhenStopped = YES;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//-- JSON Parsing
NSMutableArray *result = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:nil];
NSLog(#"settings = %#",result);
for (NSDictionary *dic in result)
{
[[NSUserDefaults standardUserDefaults] setObject:[dic objectForKey:#"footer"] forKey:#"op_footer"];
[[NSUserDefaults standardUserDefaults] synchronize];
[_indicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
View code is:
(void)viewDidLoad {
[super viewDidLoad];
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
How can I show new values without restarting application . Is there any code to be added in view ? I can see json is fetching correctly newvalues when user entered new code
Thank you.
Are you sure viewDidLoad is the best place to add this code?.
Try to add your code in some method which is called more often. For debugging I usually add some button just to call a NSLog to see what happens.
Try this if you want to load the text all the time you come to the viewcontroller.
- (void)viewWillAppear:(BOOL)animated {
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
}
Or you can try this if you want to load it to a label after getting the new code.
- (void)updateLabel {
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
}
Hope this helps.. :)
Why not move this line...
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
to here (unless of course this is in a different class)...
NSMutableArray *result = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:nil];
NSLog(#"settings = %#",result);
for (NSDictionary *dic in result)
{
[[NSUserDefaults standardUserDefaults] setObject:[dic objectForKey:#"footer"] forKey:#"op_footer"];
[[NSUserDefaults standardUserDefaults] synchronize];
[_indicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
//Update it straight away
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
Also bit confused why you are looping through dictionaries here? Is there more than one dictionary? If there is the last value will always be the one that gets stored in UserDefaults.
You are also stopping the animation in the loop as well?
The user defaults plist can take time to save and update. It doesn't happen instantly. Try triggering the call after a pause of some kind. Test with a button.
Edit: you can add an observer to the default so that you know it has updated - Cocoa - Notification on NSUserDefaults value change?
I am developing an app that communicates with a server, and saves to core data.
in appDelegate, I started my singleton object and called useDocument to initialize UIManagedDocument:
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:self.database.fileURL.path]){
[self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:nil];
} else if (self.database.documentState == UIDocumentStateClosed){
[self.database openWithCompletionHandler:nil];
}
}
heres the code upon receiving data from server:
- (void)downloadCompletedWithData: (NSData *)data item: (TPDownloadItem *)finishedItem;
{
// parse data and update core
NSError *error;
id dataObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (dataObject && !error){
dispatch_async(dispatch_get_main_queue(), ^{
[User createUserWithInfo:dataObject inManagedContext:self.database.managedObjectContext];
});
}
}
heres the code that writes to core data:
+ (id)createUserWithInfo: (NSDictionary *)userInfo inManagedContext: (NSManagedObjectContext *)aContext
{
NSError *error;
User *aUser = [NSEntityDescription insertNewObjectForEntityForName:ENTITY_NAME inManagedObjectContext:aContext];
NSString *userID = [userInfo objectForKey:USER_ID];
aUser.userID = userID;
aUser.effective = [NSNumber numberWithBool:[[userInfo objectForKey:USER_EFFECTIVE] boolValue]];
aUser.job = [userInfo objectForKey:USER_JOB];
aUser.gender = [userInfo objectForKey:USER_GENDER];
return aUser;
}
The problem is that whenever core data tries to auto save (waited for about 30 sec) the app crashes with the following error:
I tried to call "useDocument" and "createUser" in the main thread using GCD, also override UIDocument "contentsForType" to make sure it happens right after autosave, document state is normal before it saves, and persistence store file is also created.
Does anyone encounter similar situation? any help is very much appreciated!!
managed context is not thread safe, but you can have as many managed contexts as you like, that might have something to do with this. Perhaps remove the dispatch async, you don't need it as you are doing the code on main thread.
I make a background managed context in a data manager class, where I do all my writes and when I need to, I listen do a notification name MANAGEDCONTEXTDIDSAVE and reload if I need to.
Works like a charm.
the problem is sort of fixed, I had to turn off and reset my phone to get it to work...
but still don't know why this could happen