iCloud UIManagedDocument EXC_BAD_ACCESS error after deleting app and reloading - ios

I have an app that I'm making and it uses a UIManagedDocument to handle core data and iCloud. At the start of my app I Open the file and initialize the UIManagedDocument with the iCloud directory.
This works fine the first time I load the program and subsequent times but It is my understanding that because I use iCloud I should be able to delete the app and reload it and have it reload the data from the cloud.
When I delete the app and reload it its able to get the url and initialize the UIManagedDocument but once it gets to [document openWithCompletionHandler:^(BOOL success){}] the thread that was supposed to access the document comes back with EXC_BAD_ACCESS.
This is the code I use to setup the document with core data and the iCloud url is good. This is my first experience with iCloud. What am I doing wrong?
- (void)setupDocument
{
NSURL *url = [self iCloudDocumentsDirectory];
url = [url URLByAppendingPathComponent:#"ZJournalData"];
UIManagedDocument *document = [[ZManagedDocument alloc] initWithFileURL:url];
NSLog(#"%#", document);
[document.persistentStoreOptions setValue:[document.fileURL lastPathComponent] forKey:NSPersistentStoreUbiquitousContentNameKey];
[document.persistentStoreOptions setValue:[[self iCloudDirectory] URLByAppendingPathComponent:#"CoreDataLogs"] forKey:NSPersistentStoreUbiquitousContentURLKey];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
NSLog(#"%#", document);
self.managedObjectContext = document.managedObjectContext;
}
}

Related

UIManagedDocument saveToURL completionHandler is not called - Error message: "The reader is not permitted to access the URL."

I've got an old app that uses UIManagedDocument to interact with Core Data. However on iOS 11.2 (and possibly earlier iOS 11 point releases) the saveToURL:forSaveOperation:completionHandler: method seems to have stopped working both on-device and in the simulator (however it does still work in the iOS 10.3.1 simulator). Specifically, in the code below the completionHandler inside the first if statement is never executed (as NSLog messages indicate).
- (void)useDemoDocument {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"TWL_Document"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
NSLog(#"This Code Executes");
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
NSLog(#"But this is never called");
self.managedObjectContext = document.managedObjectContext;
} else {
NSLog(#"This also is not called");
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
self.managedObjectContext = document.managedObjectContext;
}
}
Instead I get an error message that The reader is not permitted to access the URL.:
2017-12-17 12:38:14.258936-0800 ToWatchList[1864:542434] [default] [ERROR] Could not get attribute values for item /var/mobile/Containers/Data/Application/2[UUID]/Documents/TWL_Document (n). Error: Error Domain=NSFileProviderInternalErrorDomain Code=1 "The reader is not permitted to access the URL." UserInfo={NSLocalizedDescription=The reader is not permitted to access the URL.}
What's going on here? Any suggestions on how to get this working again in iOS 11?

Objective C: Move local core data store to icloud and vice versa

I'm having problems with this and read now several tutorials and also examples in one book, which however suggested more a deep copy which is too complicated for me. This iCloud stuff is anyway confusing. Did I get it right with this:
User enabling iCloud in app:
- Try to copy local store to icloud
- Deleting local store
User disabling iCloud in app:
- Try to copy store to local
- Remove ubiquitous content
- Remove local copy of icloud
Do I miss anything?
Extra questions: How can I "block" the app during copying and where should I place this "blocking" code?
This would be my code:
- (bool)moveStoreToICloud {
NSLog(<#NSString * _Nonnull format, ...#>)(#" called");
return [self moveStoreFileToICloud:self.store delete:YES backup:YES];
}
- (bool)moveStoreFileToICloud:(NSURL*)fileURL delete:(bool)shouldDelete backup:(bool)shouldBackup {
NSLog(#" called");
// Always make a backup of the local store before migrating to iCloud
if (shouldBackup)
[self backupLocalStore];
NSPersistentStoreCoordinator *migrationPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model];
// Open the existing local store using the original options
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:ubiquitousContentNameKey
//,NSPersistentStoreUbiquitousContentURLKey:#"ChangeLogs" // Optional since iOS7
};
id sourceStore = [migrationPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:fileURL options:options error:nil];
if (!sourceStore) {
NSLog(#" failed to add old store");
return FALSE;
} else {
NSLog(#" Successfully added store to migrate");
bool moveSuccess = NO;
NSError *error;
NSLog(#" About to migrate the store...");
// Now migrate the store using the iCloud options
id migrationSuccess = [migrationPSC migratePersistentStore:sourceStore toURL:[_iCloudStore URL] options:options withType:NSSQLiteStoreType error:&error];
if (migrationSuccess) {
moveSuccess = YES;
NSLog(#"store successfully migrated");
// Now delete the local file
if (shouldDelete) {
NSLog(#" deleting local store");
[self destroyAllLocalDataForThisApplication];
} else {
NSLog(#" not deleting local store");
}
[self resetCoreData];
[self setupCoreData];
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingChanged"
object:nil];
return TRUE;
}
else {
NSLog(#"Failed to migrate store: %#, %#", error, error.userInfo);
return FALSE;
}
}
return FALSE;
}
/*! Moves an iCloud store to local by migrating the iCloud store to a new local store and then removes the store from iCloud.
Note that even if it fails to remove the iCloud files it deletes the local copy. User may need to clean up orphaned iCloud files using a Mac!
#return Returns YES of file was migrated or NO if not.
*/
- (bool)moveStoreToLocal {
NSLog(#"moveStoreToLocal called");
// Lets use the existing PSC
NSPersistentStoreCoordinator *migrationPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model];
// Open the store
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:ubiquitousContentNameKey
//,NSPersistentStoreUbiquitousContentURLKey:#"ChangeLogs" // Optional since iOS7
};
id sourceStore = [migrationPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[_iCloudStore URL] options:options error:nil];
if (!sourceStore) {
NSLog(#" failed to add old store");
return FALSE;
} else {
NSLog(#" Successfully added store to migrate");
bool moveSuccess = NO;
NSError *error;
NSLog(#" About to migrate the store...");
id migrationSuccess = [migrationPSC migratePersistentStore:sourceStore toURL:[_store URL] options:options withType:NSSQLiteStoreType error:&error];
if (migrationSuccess) {
moveSuccess = YES;
NSLog(#"store successfully migrated");
// Now delete the local file
[self destroyAlliCloudDataForThisApplication];
[self resetCoreData];
[self setupCoreData];
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingChanged"
object:nil];
return TRUE;
}
else {
NSLog(#"Failed to migrate store: %#, %#", error, error.userInfo);
return FALSE;
}
}
return TRUE;
}
#pragma mark - ICLOUD RESET
- (void)destroyAllLocalDataForThisApplication {
if (![[NSFileManager defaultManager] fileExistsAtPath:[[_store URL] path]]) {
NSLog(#"Skipped destroying content, _store.URL is %#",[[_store URL] path]);
return;
}
NSLog(#"\n\n\n\n\n **** Destroying ALL local content for this application, this could take a while... **** \n\n\n\n\n\n");
[self removeAllStoresFromCoordinator:_coordinator];
[self removeAllStoresFromCoordinator:_seedCoordinator];
_coordinator = nil;
_seedCoordinator = nil;
NSError *error;
if([[NSFileManager defaultManager] removeItemAtURL:_storeURL error:&error]){
NSLog(#"Local store successfully removed");
} else {
NSLog(#"\n\n **** FAILED to destroy this application's iCloud content at URL (%#) **** \n%#\n",[_store URL],error);
}
}
- (void)destroyAlliCloudDataForThisApplication {
if (![[NSFileManager defaultManager] fileExistsAtPath:[[_iCloudStore URL] path]]) {
NSLog(#"Skipped destroying iCloud content, _iCloudStore.URL is %#",[[_iCloudStore URL] path]);
return;
}
NSLog(#"\n\n\n\n\n **** Destroying ALL iCloud content for this application, this could take a while... **** \n\n\n\n\n\n");
[self removeAllStoresFromCoordinator:_coordinator];
[self removeAllStoresFromCoordinator:_seedCoordinator];
_coordinator = nil;
_seedCoordinator = nil;
NSDictionary *options =
#{
NSPersistentStoreUbiquitousContentNameKey:ubiquitousContentNameKey
//,NSPersistentStoreUbiquitousContentURLKey:#"ChangeLogs" // Optional since iOS7
};
NSError *error;
if ([NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:[_iCloudStore URL]
options:options
error:&error]) {
NSLog(#"\n\n\n\n\n");
NSLog(#"* This application's iCloud content has been destroyed *");
NSLog(#"* On ALL devices, please delete any reference to this application in *");
NSLog(#"* Settings > iCloud > Storage & Backup > Manage Storage > Show All *");
NSLog(#"\n\n\n\n\n");
[[NSFileManager defaultManager] removeItemAtURL:[_iCloudStore URL] error:&error]
} else {
NSLog(#"\n\n **** FAILED to destroy this application's iCloud content at URL (%#) **** \n%#\n",[_iCloudStore URL],error);
}
}
If this is a new app, rethink your approach. Use CloudKit, or Azure, or Firebase.
iCloud Core Data never worked reliably. Thankfully Apple now realizes this. NSPersistentStoreUbiquitousContentNameKey is marked as deprecated as of iOS 10/macOS 12.12, as are the other iCloud Core Data symbols.

Upload file to iCloud, sometimes file not found

I want to upload some files to iCloud and use this:
FileDocument *doc = [[FileDocument alloc] initWithFileURL:des];
doc.data = [NSData dataWithContentsOfURL:src];
NSLog(#"Local url: %#", src);
NSLog(#"Remote url: %#", des);
[doc saveToURL:des forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success) {
NSLog(#"Success");
} else {
NSLog(#"Fail ");
}
}];
After the success log, I open iCloud > Storage > Manage Storage on my iPad to check if file exist. Sometimes the file appear and sometimes not.

How to do incremental writing for UIDocument in iCloud?

I have UIDocument in iCloud ubiquitous container and I need to append data to file while saving document. I override readFromURL:: and writeContents::::: method according UIDocument documentation:
-(BOOL) writeContents:(id)contents toURL:(NSURL*)url forSaveOperation:(UIDocumentSaveOperation)saveOperation originalContentsURL:(NSURL*)originalContentsURL error:(NSError *__autoreleasing *)outError
{
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error = nil;
[coordinator coordinateWritingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
NSData* data = contents; //correct, non-empty NSData
///[data writeToFile:newURL :] works, but overwrite original file
NSOutputStream* stream =[[NSOutputStream alloc] initWithURL:newURL append:YES];
if (stream)
{
NSInteger written = [stream write:data.bytes maxLength:data.length];
if (written != data.length)
{
//failed here, written == -1
NSLog(#"Write data to UIDocument failed: %#, error: %#", newURL, stream.streamError);
}
}
else
{
NSLog(#"Write data to iCloudDocument failed: %#", newURL);
}
}];
if (error)
{
NSLog(#"Coordinated write failed %#, error: %#", url, error);
*outError = error;
}
return error == nil;
}
Accessor block has different newURL, for example:
url: file:///private/var/mobile/Library/Mobile%20Documents/XXXXXXX~com~test~test/test.doc
newURL: file:///private/var/mobile/Applications/5631D484-7661-4E9E-A342-B25297FC0E18/tmp/(A%20Document%20Being%20Saved%20By%20test%20)/test.doc.
[stream write::] failed, because newURL file dosn't exists and I can't append data, only create file with all document's content.
Document editing code:
NSURL* url = [self.containerURL URLByAppendingPathComponent:kCloudDocumentName];
MyDocument* document = [[MyDocument alloc] initWithFileURL:url];
[document openWithCompletionHandler:^(BOOL success) {
if (success)
{
//update some document data
[self updateData:document completion:nil];
[document closeWithCompletionHandler:^(BOOL success) {
//failed here!
}];
}
}];
MyDocument exists in ubiquitous container at url and document has Normal state.
How I can do incremental writing in this case? Whats wrong?
After changing data in your UIDocumnet you can use:
-[UIDocumnet saveToURL:UIDocumnet.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
Not sure, it's right or not but it works in my app. If it's wrong, correct me please somebody.
You may need to overwrite this method in your subclass of UIDocumnet:
- (id) contentsForType:(NSString *)typeName error:(NSError **)outError

When I open a NSManagedDocument (core data) file, it always seems empty

There is something I dont understand in core data.
I have created a NSManagedDocument, made a change, and saved it.
Then I created another one and opened the file I saved.
From what I understand the NSManagedDocument should have the change I mad, but it doesn't.
here is the code I wrote:
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Data Base"];
// url is now "<Documents Directory>/Default Photo Database"
self.dataBase = [[UIManagedDocument alloc] initWithFileURL:url];
if (self.dataBase.documentState == UIDocumentStateClosed) {
[self.dataBase openWithCompletionHandler:^(BOOL success) {
}];
}
Position *pos = [NSEntityDescription insertNewObjectForEntityForName:#"Position" inManagedObjectContext:self.dataBase.managedObjectContext];
pos.name = #"positon 1";
[self.dataBase saveToURL:self.dataBase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success){
}];
self.dataBase = [[UIManagedDocument alloc] initWithFileURL:url];
if (self.dataBase.documentState == UIDocumentStateClosed) {
[self.dataBase openWithCompletionHandler:^(BOOL success) {
}];
}
pos = [NSEntityDescription insertNewObjectForEntityForName:#"Position" inManagedObjectContext:self.dataBase.managedObjectContext];
pos.name = #"position 2";
as far as i understand I should have 2 objects for entity "Position" by now, but I only have the last one, why is that?
what am I missing here?
I don't know why, but as soon as I moved my code to the AppDelegate it was solved...
I know this is an old thread but figured I would answer it anyway...
Opening the database is an asynchronous call. You need to move the code where you are inserting stuff into the database into the success block of the completion handler or this won't work. The way that you have it written you are trying to insert the position before the database has actually been opened.
if (self.dataBase.documentState == UIDocumentStateClosed) {
[self.dataBase openWithCompletionHandler:^(BOOL success) {
Position *pos = [NSEntityDescription insertNewObjectForEntityForName:#"Position" inManagedObjectContext:self.dataBase.managedObjectContext];
pos.name = #"positon 1";
}];
}

Resources