Random data loss on iOS after restarting app - ios

Clearly I must not be doing something right but this happens on random occasions. I can't make the issue appear by following certain steps so it has become extremely hard to debug. I have an app in which every time an object is added, or deleted it writes the file to a plist. I can verify the plist in the simulator and I also have an NSLog each time my save function is called. The data is loaded when applicationWillEnterForeground is called, which is also working correctly. On certain occasions after starting up the app it will revert to the previous save before the most recent changes. Does iOS cache data files? If it tries to load a file from disk to an array, could it possible already have the previous load in a cache and create the array with that data instead?
Save method:
- (void)saveFile {
// saves data to file
NSLog(#"save file reached");
#ifdef LITE_VERSION
[self.aquariums writeToFile:[self dataFilePathLite] atomically:YES];
#else
[self.aquariums writeToFile:[self dataFilePath] atomically:YES];
#endif
}
Load method; I just added the if (self.aquariums == null) check and it might have solved the issue but it's hard for me to confirm since I can't recreate the problem:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
NSLog(#"applicationDidBecomeActive called");
if (self.aquariums == NULL) {
NSLog(#"aquariums null, loading file");
#ifdef LITE_VERSION
NSString *filePath = [self dataFilePathLite];
#else
NSString *filePath = [self dataFilePath];
#endif
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
self.aquariums = array;
[array release];
[self update21];
} else {
#ifdef LITE_VERSION
[self convertFileName];
#else
[self update20];
#endif
}
application.applicationIconBadgeNumber = 0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
aquaPlannerAppDelegate_iPad *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.rootView viewDidAppear:YES];
}
}
}

After releasing and not getting any reports of loss of data the issue must definitely have been
if (self.aquariums == NULL) {
so people remember if you are going to reload data when an app returns from being in the background set it to null first and release
aquariums = NULL;
[aquariums release];

Related

How to save a .plist file in iOS (e.g. iPhone)?

I am new to iOS app development and I made an app which has a text field, an insert button and a table.
So, when something is entered into the text field, it is displayed on the table.
When the app enters background or terminates, it save that list as follows:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSBundle *myApp = [NSBundle mainBundle];
NSMutableString *fileDirectory = [[NSMutableString alloc] initWithString:[myApp bundlePath]];
[fileDirectory appendString:#"/list.plist"];
NSLog(#"%#", fileDirectory); //Just for refernece
[self.viewController.tasks writeToFile:fileDirectory atomically:YES];
NSLog(#"SAVED");
}
and
- (void)applicationWillTerminate:(UIApplication *)application {
NSBundle *myApp = [NSBundle mainBundle];
NSMutableString *fileDirectory = [[NSMutableString alloc] initWithString:[myApp bundlePath]];
[fileDirectory appendString:#"/list.plist"];
NSLog(#"%#", fileDirectory); //Just for refernece
[self.viewController.tasks writeToFile:fileDirectory atomically:YES];
NSLog(#"SAVED");
}
The file is saved.
After killing the app and opening the app again, I don't get the list back (only on my iPhone, on simulator it works fine)
Is it possible to retain the list on iPhone?
P.S: Sorry if I'm being verbose. Thank you.
The method -writeToFile:atomically: returns a BOOL indicating if the write operation was successful.
In your case, it will be successful on the simulator, as your are writing to your Mac's disk, which can always be done, as your process has write-privileges to your application's bundle. This is not the case on the device: because the app is code-signed the bundle is readonly, and you cannot write a file into it, so the method will return NO.
You should always check this value, and respond appropriately. There is also a method that takes an NSError pointer, so you can see why a write operation failed.
Remember that -writeToFile:atomically: is a synchronous operation, and it will block the thread on which it is called. In general, you want to avoid blocking the main thread, and therefore it is best to run the method on another thread (look up dispatch_async() and GCD documentation). In your case, your are calling it in applicationWillTerminate:, which is the last breath of your application and you do not want to dispatch to another thread, as it will die with the after application after applicationWillTerminate: returns, and most likely before that secondary thread had a chance to finish. Note, however, that applicationWillTerminate: might not be the best place to save state in, as time is short there, but also because this method is only called when your application will really terminate (it is not called when you press the home-button for example). Look up the documentation on the other application-lifetime methods to find out where to save your date best, you might find that you don't even need to use those methods, but saving can be done 'on th fly' while the app is running. Whatever suits your purposes.
To get a directory to which you can write, you can use for example this code:
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory: NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
Just load the file back in applicationDidBecomeActive or applicationDidFinishLaunching, assuming the file was successfully created and it didn't somehow get deleted between the time you closed the app and restarted it, which shouldn't happen, unless you're cleaning the build container files explicitly, but I doubt it.
So, I fixed the issue.
The code now saves the file asynchronously in applicationDidEnterBackground as follows:
- (void)applicationDidEnterBackground:(UIApplication *)application {
typedef void (^block)();
block theBlock;
theBlock = ^(){
BOOL fileWritten = [self.viewController.tasks writeToFile:self.viewController.savedList atomically:YES];
if(fileWritten)
{
NSLog(#"Saved");
}
};
/* Saving file Asynchronorusly */
dispatch_queue_t myQueue = dispatch_queue_create("QUEUE-1",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myQueue, theBlock);
}
and synchronously in applicationWillTerminate as follows:
- (void)applicationWillTerminate:(UIApplication *)application {
/* Saving file Synchronously */
BOOL fileWritten = [self.viewController.tasks writeToFile:self.viewController.savedList atomically:YES];
if(fileWritten)
{
NSLog(#"Saved");
}
}
Those who pointed out that I didn't have any retrieval code, I am sorry I forgot to mention it. It loads the .plist in applicationDidFinishLaunching as follows:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.viewController myButton:nil];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
NSURL *fileDirectory = self.viewController.applicationDocumentsDirectory;
NSMutableString *filePath = [[NSMutableString alloc] initWithString:[fileDirectory absoluteString]];
[filePath appendString:#"list.plist"];
filePath = (NSMutableString *)[filePath stringByReplacingOccurrencesOfString:#"file:///" withString:#""];
self.viewController.savedList = filePath;
if([[NSFileManager alloc] fileExistsAtPath:self.viewController.savedList])
{
self.viewController.tasks = [[NSMutableArray alloc] initWithContentsOfFile:self.viewController.savedList];
NSLog(#"DATA READ: %#", self.viewController.tasks);
}
[self.window makeKeyAndVisible];
return YES;
}
So, now the app works as expected. Thank you guys.

Google Analytics SDK creates SQLite errors

Updates
19th August 2015 - Bug seems to have now been fixed in the 3.13 update, even though the only thing they list in their changelog is "Fixed an issue which caused crashes when using setCampaignParametersFromUrl". Take that as you will.
8th June 2015 - Still encountering this problem. If I disable automatic sending of events ([GAI sharedInstance].dispatchInterval = -1;) then I still receive errors. I assume, therefore, that the problem lies with inserting the event into the Google Analytics SQLite database, that somehow my own database statement that is currently in progress are becoming void.
10th June 2015 - Still encountering crashes. Tried removing my controllers extending GAITrackedViewController and sending the createScreenView track manually with no change in crash frequency.
25th June 2015 - Still encountering crashes.
Intro
I have added the Google Analytics SDK 3.12 to my iPhone app and everything is working as expected - I run the app and can see all of the hits and events that I have setup coming through on the Web interface.
I am initialising the SDK in my AppDelegate right at the top of my didFinishLaunchingWithOptions, like so:
[[GAI sharedInstance] trackerWithTrackingId:GOOGLE_ANALYTICS_ID];
The problem
However, I have found that running Google Analytics creates errors when I try and use SQLite for myself. They can manifest as serious errors such as:
"Database disk image is malformed" and then insta-crashes
"Disc i/O error" whenever I run a query (though doesn't crash)
And they can also cause my own SQLite queries to just fail, for instance:
if (! sqlite3_prepare_v2(_db, [sql UTF8String], -1, &_statement, NULL) == SQLITE_OK) {`
// ..
// ..
if (sqlite3_step(_statement) == SQLITE_ROW) {
Will result, randomly, in the following error:
sqlite3_prepare_v2 EXC_BAD_ACCESS (code=1, address=0x6800000000)
If I comment out the SDK initialisation then everything goes back to being incredibly stable. Uncomment it again and it will crash the app within a minute.
Pre-emptive question answering
Am running this on a iPhone 6 running 8.3 (12F70).
Have tried uninstalling and reinstalling the app.
I have added all of the pre-requisites for Google Analytics to work; all of the .m files to the library, the libGoogleAnalyticsServices.a file, and also the Linked Frameworks and Libraries.
I also have Crashlytics, but have tried commenting it out from the code ([Fabric with:#[CrashlyticsKit]];) and removing its library from the Linked Frameworks and Libraries with exactly the same results.
Code
Setting up the class
// In didFinishLaunchingWithOptions
[Db setup];
[Db connect];
Accesses the class
Db * db = [[Db alloc] init];
if ([db prepare:#"SELECT * FROM `table` WHERE `id` = ?" withBindings:#[#"123"]]) {
while ([db stepThrough]) {
// ..
}
}
[db finalise];
The class
(Have indicated where the errors appear with comments)
#implementation Db
static sqlite3 * _db;
static NSString * _dbPath;
#pragma mark - Setup
+ (BOOL)setup {
NSString * sqlBundlePath = [[NSBundle mainBundle] pathForResource:#"db" ofType:#"sqlite"];
NSString * documentsFolder = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString * sqlDocumentPath = [[documentsFolder stringByAppendingPathComponent:#"db"] stringByAppendingPathExtension:#"sqlite"];
NSFileManager * fileManager = [NSFileManager defaultManager];
if (! [fileManager fileExistsAtPath:sqlDocumentPath]) {
NSError * error;
BOOL success = [fileManager copyItemAtPath:sqlBundlePath toPath:sqlDocumentPath error:&error];
if (! success) {
return NO;
}
}
_dbPath = sqlDocumentPath;
return YES;
}
+ (BOOL)connect {
sqlite3_config(SQLITE_CONFIG_SERIALIZED);
return sqlite3_open([_dbPath UTF8String], &_db);
}
#pragma mark - Querying
- (BOOL)prepare:(NSString *)sql withBindings:(NSArray *)bindings {
// ERROR CAN OCCUR ON THE FOLLOWING LINE
if (! sqlite3_prepare_v2(_db, [sql UTF8String], -1, &_statement, NULL) == SQLITE_OK) {
NSLog(#"Error whilst preparing query: %s", sqlite3_errmsg(_db));
sqlite3_finalize(_statement);
return NO;
}
for (int i = 0; i < [bindings count]; i++) {
sqlite3_bind_text(_statement,
i + 1,
[bindings[i] isKindOfClass:[NSNull class]] ? [#"" UTF8String] : [bindings[i] UTF8String],
-1,
SQLITE_TRANSIENT);
}
return YES;
}
- (BOOL)stepThrough {
// ERROR CAN OCCUR ON THE FOLLOWING LINE
if (sqlite3_step(_statement) == SQLITE_ROW) {
return YES;
}
sqlite3_finalize(_statement);
return NO;
}
- (void)finalise {
sqlite3_finalize(_statement);
}
#end
Upgrading to the new version of the SDK (3.13) fixed this issue (at least for me), even though their changelog does not specifically mention it.

How to display an AlertView requesting permission to seed iCloud once and only once in app's iCloud lifetime?

I have one question near the end.
I am working from the belief/experience that seeding iCloud more than once is a bad idea and that if a user can do the wrong thing, he probably will sooner or later.
What I want to do:
A. When the user changes the app preference "Enable iCloud" from NO to YES, display AlertView asking (Yes or No) if the user wishes to seed the cloud with existing non-iCloud Data.
B. Ensure that the app seeds iCloud only once on an iCloud account, refraining to put up the AlertView once seeding is completed the first time.
My Method:
Following Apple's Docs concerning the proper use of NSUbiquitousKeyValueStore, I am using the following method in, - (void)application: dFLWOptions:
- (void)updateKVStoreItems:(NSNotification*)notification {
// Get the list of keys that changed.
NSDictionary* userInfo = [notification userInfo];
NSNumber* reasonForChange = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
NSInteger reason = -1;
// If a reason could not be determined, do not update anything.
if (!reasonForChange)
return;
// Update only for changes from the server.
reason = [reasonForChange integerValue];
if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
(reason == NSUbiquitousKeyValueStoreInitialSyncChange)) { // 0 || 1
// If something is changing externally, get the changes
// and update the corresponding keys locally.
NSArray* changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
// This loop assumes you are using the same key names in both
// the user defaults database and the iCloud key-value store
for (NSString* key in changedKeys) {//Only one key: #"iCloudSeeded" a BOOL
BOOL bValue = [store boolForKey:key];
id value = [store objectForKey:#"iCloudSeeded"];
[userDefaults setObject:value forKey:key];
}
}
}
Include the following code near the top of application: dFLWO:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateKVStoreItems:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store]; // add appDelegate as observer
After loading iCloud Store, then seed it with non-iCloud data ONLY if seeding has never been done
- (BOOL)loadiCloudStore {
if (_iCloudStore) {return YES;} // Don’t load iCloud store if it’s already loaded
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:#"MainStore"
};
NSError *error=nil;
_iCloudStore = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:[self iCloudStoreURL] options:options error:&error];
if (_iCloudStore) {
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded =
[store boolForKey:#"iCloudSeeded"];//If the key was not found, this method returns NO.
if(!iCloudSeeded) // CONTROL IS HERE
[self confirmMergeWithiCloud]; // Accept one USER confirmation for seeding in AlertView ONCE world wide
return YES; // iCloud store loaded.
}
NSLog(#"** FAILED to configure the iCloud Store : %# **", error);
return NO;
}
Once the seeding is completed do the following to prevent any repeat seeding:
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloud];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"]; // NEVER AGAIN
//[store synchronize];
}
}
}
Be sure to get a total iCloud reset before the above process using:
[NSPersistentStoreCoordinator
removeUbiquitousContentAndPersistentStoreAtURL:[_iCloudStore URL]
options:options
error:&error])
This is a very tidy solution to my problem, IMHO, but I can not quite get it done.
MY QUESTION:
How do I respond to the first notification to updateKVStoreItems: above? It is a notification with bad info. I says the value is TRUE, but I have never set it to TRUE. How do I set default values for a key in NSUbiquitousKeyValueStore?
I find that the first notification is of reason : NSUbiquitousKeyValueStoreInitialSyncChange
When that note comes in, bValue is YES. THIS IS MY PROBLEM. It is as if, iCloud/iOS assumes any new BOOL to be TRUE.
I need this value to be NO initially so that I can go ahead and follow the Apple Docs and set
the NSUserDefault to NO. And then Later when the seeding is done, to finally set the value: YES for the key:#"iCloudSeeded"
I find I can not penetrate the meaning of the following from Apple:
NSUbiquitousKeyValueStoreInitialSyncChange
Your attempt to write to key-value storage was discarded because an initial download from iCloud has not yet happened.
That is, before you can first write key-value data, the system must ensure that your app’s local, on-disk cache matches the truth in iCloud.
Initial downloads happen the first time a device is connected to an iCloud account, and when a user switches their primary iCloud account.
I don't quite understand the implications of number 2 below, which I found online:
NSUbiquitousKeyValueStoreInitialSyncChange – slightly more complicated, only happens under these circumstances:
1. You start the app and call synchronize
2. Before iOS has chance to pull down the latest values from iCloud you make some changes.
3. iOS gets the changes from iCloud.
If this problem was with NSUserDefaults and not NSUbiquitousKeyValueStore, I believe I would need to go to registerDefaults.
I am almost there,
How do I do this please!
Thanks for reading, Mark
The code was looking for both
A. NSUbiquitousKeyValueStoreInitialSyncChange and
B. NSUbiquitousKeyValueStoreServerChange
I was unable to figure out what to do with the notifications. I know see that I did not need to do anything with either. My app only needs to read and write, in order to solve the problem I laid out in my question header.
The app gets the current value with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded = [store boolForKey:#"iCloudSeeded"];
The app sets the value in the NSUbiquitousKeyValueStore with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"];
I believe I am correct in saying the following: Writing is done into memory. Very soon thereafter the data is put by the system onto disk.
From there it is taken and put into iCloud and is made available to the other devices running the same app on the same iCloud account. In the application I have described, no observer needs to be added, and
nothing else needs to be done. This is maybe an "unusual" use of NSUbiquitousKeyValueStore.
If you came here looking for a an more "usual" use, say when a user type something into a textview and it later
appears on a view of other devices running the same app, check out a simple demo I came across at :
https://github.com/cgreening/CMGCloudSyncTest
The better functioning (monitoring only) notification handler follows:
- (void)updateKVStoreItems:(NSNotification*)notification {
NSNumber *reason = notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey];
if(!reason) return;
// get the reason code
NSInteger reasonCode = [notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] intValue];
BOOL bValue;
NSUbiquitousKeyValueStore *store;
switch(reasonCode) {
case NSUbiquitousKeyValueStoreServerChange:{ // code 0, monitoring only
store = [NSUbiquitousKeyValueStore defaultStore];
bValue = [store boolForKey:#"iCloudSeeded"];
id value = [store objectForKey:#"iCloudSeeded"];
DLog(#"New value for iCloudSeeded=%d\nNo Action need be take.",bValue);
// For monitoring set in UserDefaults
[[NSUserDefaults standardUserDefaults] setObject:value forKey:#"iCloudSeeded"];
break;
}
case NSUbiquitousKeyValueStoreAccountChange: {// ignore, log
NSLog(#"NSUbiquitousKeyValueStoreAccountChange");
break;
}
case NSUbiquitousKeyValueStoreInitialSyncChange:{ // ignore, log
NSLog(#"NSUbiquitousKeyValueStoreInitialSyncChange");
break;
}
case NSUbiquitousKeyValueStoreQuotaViolationChange:{ // ignore, log
NSLog(#"Run out of space!");
break;
}
}
}
Adding 9/3/14
So sorry but I continued to have trouble using a BOOL, I switched to an NSString and now
all is well.
METHOD TO ENSURE THAT THE "MERGE" BUTTON FOR SEEDING ICOUD IS USED AT MOST ONCE DURING APP LIFETIME
Use NSString and not BOOL in KV_STORE. No need to add observer, except for learning
In Constants.h :
#define SEEDED_ICLOUD_MSG #"Have Seeded iCloud"
#define ICLOUD_SEEDED_KEY #"iCloudSeeded"
Before calling function to seed iCloud with non-iCloud data:
NSUbiquitousKeyValueStore* kvStore = [NSUbiquitousKeyValueStore defaultStore];
NSString* strMergeDataWithiCloudDone =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
NSComparisonResult *result = [strMergeDataWithiCloudDone compare:SEEDED_ICLOUD_MSG];
if(result != NSOrderedSame)
//put up UIAlert asking user if seeding is desired.
If user chooses YES : set Value for Key after the merge is done.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloudwithNoniCloudData];
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
[store setObject:SEEDED_ICLOUD_MSG forKey:ICLOUD_SEEDED_KEY];
}
}
}
Thereafter on all devices, for all time, the code
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
NSString* msg =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
produces: msg == SEEDED_ICLOUD_MESSAGE

Message sent to deallocated instance VERY WEIRD ERROR

I'm using Xcode 5.02 and iOS 7.04 and I've been searching long and hard to solve this annoying bug, and after many hours of debugging, I still cannot squash this bug.
So I'm using a UIManagedDocument Helper class in order to retrieve my data
+ (void)openDocument:(NSArray *)documentData {
NSString *documentName = documentData[0];
CompletionBlock completionBlock = documentData[1];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *url = [documentsDirectory URLByAppendingPathComponent:documentName];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
completionBlock(document);
preparingDocument = NO;
};
if(!preparingDocument){
preparingDocument = YES;
if(!([fileManager fileExistsAtPath:[url path]])){
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForCreating
completionHandler:OnDocumentDidLoad];
} else if(document.documentState == UIDocumentStateClosed){
[document openWithCompletionHandler:OnDocumentDidLoad];
} else if (document.documentState == UIDocumentStateNormal) {
OnDocumentDidLoad(YES);
}
} else {
//Try till Document is Ready
[self performSelector:#selector(openDocument:)
withObject:documentData
afterDelay:0.5];
}
}
In my view controller, I use this helper class in order to gain access to my ManagedObjectContext
- (void)updateContext{
[DocumentHelper openDocument:#[DOCUMENT_NAME, ^(UIManagedDocument *document) {
self.managedObjectContext = document.managedObjectContext;
}]];
}
And this updateContext method gets called usually upon updating the CoreData, such as adding or deleting new items, however this method is also called in the (void)viewWillAppear method and in a notification block when the Application is in the Foreground (Using the Application Delegate)
Whenever I put the application into the background and reopen the application, the application crashes saying
*** -[UIManagedDocument _setInConflict:]: message sent to deallocated instance 0x1701b0ae0
I used malloc and the NSZombie Profile manager, but no matter what this bug is like a ticking time bomb. The error occurs upon a random number of times of closing and reopening the app.
I experienced the same problem today.
* -[UIManagedDocument _setInConflict:]: message sent to deallocated instance 0x1701b0ae0
This message indicates that your UIManagedDocument instance has been deallocated but is having a message sent to it. I solved the issue in my project by declaring the document variable as a file-level variable (outside of the method) so that it would not be too-hastily released, and only setting it to nil after I was done using it.
EDIT to answer question:
My app checks and updates from an iCloud document in the app delegate. In my AppDelegate.h file, I have this:
#interface CSPAppDelegate : UIResponder <UIApplicationDelegate> {
BOOL iCloudAvailable;
NSMetadataQuery *_query;
CSPICloudDocument *doc; // <<< declare document variable here instead of in method
}
The document is instantiated in the relevant method. The only real difference between your code and what I'm doing is where I've declared the variable. This was sufficient to solve the same error for me.

Bug after app store deployment, unable to repro in Ad Hoc deployment

My application is involves users saving data which I store using NSCoding/NSKeyedArchiver. I supply the user with sample data objects on the first run of the app.
The expected behavior happens during regular testing, as well as through ad hoc deployment. Unfortunately, a significant bug happens when the app is downloaded through the app store.
What other environmental considerations might there be so I can reproduce (and then fix) the issue in my regular testing?
Expected behavior:
A new user may add/edit data objects in addition to the current ones. (A classic CRUD scenario).
Actual behavior:
If the user's first action is to save a new object, all of the previously loaded sample objects disappear (the elusive bug).
However, If the user's first action is to edit, then all of objects persist as expected, and the user can add additional ones without issue.
Thanks for the help.
EDIT
In my most recent testing, I switched the Build Configuration to release in the "Run " scheme.
http://i.imgur.com/XNyV6.png
App Delegate, which correctly initializes app
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.dataArray = nil;
self.dataArray = [AppDelegate getArray];
if (self.dataArray == nil) {
self.dataArray = [[NSMutableArray alloc] init];
}
//First run of the app
if (dataArray.count == 0) {
//Add sample data to array
//Save array
NSString *path = [AppDelegate getDocPath];
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:path];
}
}
+(NSString *) getDocPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *tempDocPath = [documentsPath stringByAppendingPathComponent:#"FilePath.dat"];
return tempDocPath;
}
+(NSMutableArray *)getArray {
return [[NSKeyedUnarchiver unarchiveObjectWithFile:[AppDelegate getDocPath]] mutableCopy];
}
Object creation, which deletes preloaded data if data hasn't been edited
-(void)viewDidLoad {
tempArray = nil;
tempArray = [NSKeyedUnarchiver unarchiveObjectWithFile:[AppDelegate getDocPath]];
if (tempArray == nil) {
tempArray = [[NSMutableArray alloc] init];
}
}
-(void)saveObject {
[tempArray addObject:createdData];
[tempArray sortUsingSelector:#selector(compare:)];
NSString *path = [AppDelegate getDocPath];
[NSKeyedArchiver archiveRootObject:tempArray toFile:path];
AppDelegate *dg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
dg.dataArray = tempArray;
}
I am not sure how to solve your current problem (without looking at the code), but here's how you can avoid it in the future:
Make sure that the build you submit to the app store is the ad-hoc build you have QA'd, but signed with an app store provisioning profile.
Two advantages:
1) You should be able to repro the same bug on the adhoc and appstore build
2) dSym for both these are the same. So, you dont have to wait to get the AppStore crash logs before you can dig in and see what's happening.
I guess while saving the new object, you are not appending it to the existing data. You might be over-writing the previously created file. You should access the previous file and append the new data to the previous file. Sharing code would help to point out where you are going wrong.
EDIT: Replace the following code and check if its still showing the same behaviour
-(void)viewDidLoad {
tempArray = nil;
tempArray = [NSKeyedUnarchiver unarchiveObjectWithFile:[AppDelegate getDocPath]mutableCopy];
if (tempArray == nil) {
NSLog(#"tempArray is nil"); //if tempArray doesn't get initialized by the file contents
tempArray = [[NSMutableArray alloc] init];
}
}

Resources