UIDocument never calling dealloc - ios

I have an issue where I can't seem to dealloc UIDocument (used in iCloud)
After running an NSMetaDataQuery to look for the document as follows..
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
[query setSearchScopes:[NSArray arrayWithObject:
NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:
#"%K == %#", NSMetadataItemFSNameKey, kFILENAME];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(queryDidFinishGathering:)
name:NSMetadataQueryDidFinishGatheringNotification
object:query];
[query startQuery];
I process my query
- (void)queryDidFinishGathering:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:query];
_query = nil;
[self loadData:query];
}
Then load or create a new document.
- (void)loadData:(NSMetadataQuery *)query {
if ([query resultCount] == 1) {
NSMetadataItem *item = [query resultAtIndex:0];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
MyDocument *doc = [[[MyDocument alloc] initWithFileURL:url] autorelease];
[doc openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"iCloud document opened %#", doc);
[doc updateChangeCount:UIDocumentChangeDone];
} else {
NSLog(#"failed opening document from iCloud");
}
}];
} else {
NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:
#"Documents"] URLByAppendingPathComponent:kFILENAME];
MyDocument *doc = [[[MyDocument alloc] initWithFileURL:ubiquitousPackage] autorelease];
[doc saveToURL:[doc fileURL]
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
[doc openWithCompletionHandler:^(BOOL success) {
NSLog(#"new document opened from iCloud");
[doc updateChangeCount:UIDocumentChangeDone];
}];
}
}];
}
}
The NSLog(#"iCloud document opened %#", doc); shows a different memory address for each UIDocument.
I have an NSLog in my UIDocument subclass, it never gets called. I cannot see where it is being retained that I am not releasing it. This query is ran whenever I want to sync my cloud data, this happens fairly regularly. The data syncs correctly.
I am experiencing strange crashes where my app will simply close to the dashboard, with nothing in the debug (from previous experiences I know this often to be the app expending too much memory and being terminated.)
I think that my UIDocument is leaking, would I be correct in this assumption, this is the first time i've wrestled with iCloud so I'm still in the dark over a few things.
My subclass has the following properties:
#property (copy, nonatomic) NSData *infoData;
#property (copy, nonatomic) NSMutableArray *firstArray;
#property (copy, nonatomic) NSMutableArray *secondArray;
#property (copy, nonatomic) NSMutableArray *thirdArray;
I am not using ARC.

I did not realise that I had to do this:
[doc updateChangeCount:UIDocumentChangeDone];
[doc closeWithCompletionHandler:nil];
Obviously if a file is open for writing, then it would not be wise to allow it to be deallocated!
Doh! Hopefully this saves someone some time in the future.

Related

How to handle remote notifications when the app is not running

If my app is running on foreground or background this working fine. I am receiving the notifications and save it on the local database. But if the app killed from the background it receives the remote notifications but the following method is not called. And the issue is if I tap any one of the notification,only that notification will saved on the local database.
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
[PFPush handlePush:userInfo];
NSLog(#"Received notification: %#", userInfo);
NSString *alertString = [[userInfo objectForKey:#"aps"]valueForKey:#"alert"];
NSLog(#"%#",alertString);
NSString *msgType = [userInfo objectForKey:#"messageType"];
NSString *senderId = [userInfo objectForKey:#"senderId"];
NSString *receverId = [userInfo objectForKey:#"receverId"];
NSString *msg = [userInfo objectForKey:#"message"];
NSString *timeStr = [userInfo objectForKey:#"Time"];
NSLog(#"msg type%# senderId %# receverId %# message %#",msgType,senderId,receverId,msg);
if ([AppDelegate isNetworkReachable]){
if ([msgType isEqualToString:#"CHAT"]) {
Chatmessage *Cmsg=[[Chatmessage alloc]init];
Cmsg.chat_date =timeStr;
Cmsg.chat_image =#"";
Cmsg.chat_message = msg;
Cmsg.chat_Receiver_Id = receverId;
Cmsg.chat_Sender_Id = senderId;
NSLog(#"recid%#",Cmsg.chat_Receiver_Id);
NSMutableArray *arryMsg = [[NSMutableArray alloc]init];
arryMsg = [[DBModelNew database]getChatMessageBasedOnTime:receverId SenId:senderId time_stamp:timeStr message:msg];
if (arryMsg.count == 0) {
[[DBModelNew database]insertmsg:Cmsg];
}
[[NSNotificationCenter defaultCenter]postNotificationName:#"receivedmessage" object:nil];
chatHistory *chatObj = [[chatHistory alloc]init];
chatObj.chat_meta_id = [NSString stringWithFormat:#"%#",senderId];
chatObj.last_send_message = msg;
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy-MM-dd HH:mm:ss";
NSString *str=[dateFormatter stringFromDate:[NSDate date]];
chatObj.last_time_stamp = [self dateformat:str];
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:senderId];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (NSDictionary *dict in objects) {
[[dict objectForKey:#"ProfilePic"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
chatObj.fndimage = image;
chatObj.name = [dict objectForKey:#"name"];
[[DBModelNew database]insertChat:chatObj];
[[NSNotificationCenter defaultCenter]postNotificationName:#"receivedNewMessage" object:nil];
}
}
}
}];
}
}
}];
}
}
}
From the Apple docs, if the user hard closes the app it does not call the method.
In addition, if you enabled the remote notifications background mode,
the system launches your app (or wakes it from the suspended state)
and puts it in the background state when a remote notification
arrives. However, the system does not automatically launch your app if
the user has force-quit it. In that situation, the user must relaunch
your app or restart the device before the system attempts to launch
your app automatically again.
If you want to launch specific payload dictionary from viewDidLoad then you simply call the following :
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
And you get the userInfo this way:
NSString *msgType = [localNotif objectForKey:#"messageType"];
And now you can act accordingly. This is just for the circumstances you stated in your title. When the app is not 'running' (terminated)

RESTKit: NSFetchedResultsController does not populate tableView after recreating the managedObjectStore

I build my TabBar programmatically and based on if the User is loggedIn, I set
[self.window setRootViewController:home];
Here is the code I call in:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
if (![Persistence loggedIn])
{
[self showLoginScreen];
}
else
{
SignupBase *login = [STLoginSignupBase new];
[login loginUserwithUsername:[Persistence username] andPassword:[Persistence authPass] requestByNewUser:NO completionBlock:^(NSError *error)
{
if (!error)
{
[login loginSuccess];
[self showTabBarScreen];
}
else
{
[STAlertViewUtils showAlert:#"" :error.localizedDescription :kButtonTitleDismiss];
[self showLoginScreen];
}
}];
}
-(void)showTabBarScreen
{
dispatch_async(dispatch_get_main_queue(), ^{
TabBarVC *tabBarVC = [[TabBarVC alloc]init];
[self.window setRootViewController:tabBarVC];
});
}
-(void)showLoginScreen
{
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"STLoginSignup" bundle:nil];
HomeVC *homeVC = (HomeVC *)[storyboard instantiateViewControllerWithIdentifier:[HomeVC storyboardID]];
UINavigationController *home = [[UINavigationController alloc]initWithRootViewController:homeVC];
[self.window setRootViewController:home];
});
}
In the TabBar, the first tab "Inbox" is a tableViewController managed with NSFetchedResultsController. When I launch the app for the first time, all the objects are fetched and displayed in the tableView beautifully; however, when I logout and login back in, and "Inbox" is reloaded, I get a blank tableView. Zero objects are fetched locally and even if RESTkit fetches objects, they don't appear in the tableView. When I stop the app in the simulator and relaunch it, all the objects are fetched locally and remotely, and appear in the tableView as they should!
Here is how I logout from the Profile tab (different tab):
- (void)logoutWithCompletionBlock:(void(^)(void))completionBlock
{
[Persistence setLoggedInStatus:NO];
RKObjectManager *objectManager = [self getObjectManager];
[objectManager.HTTPClient clearAuthorizationHeader];
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = delegate.managedObjectStore.mainQueueManagedObjectContext;
[managedObjectContext reset];
[delegate deregisterWithUrbanAirship];
if (completionBlock)
{
completionBlock();
}
}
After I log back in the App, "Inbox" tab viewController is loaded again.
In my "Inbox" loadView which gets called, I have the following code:
- (void)loadView
{
[self getManagedObjectFromAppDelegate]
}
- (void)getManagedObjectFromAppDelegate
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate setupCoreDataWithRESTKit];
self.objectManager = [self getObjectManager];
self.objectManager.managedObjectStore = appDelegate.managedObjectStore;
self.objectManager.managedObjectStore.managedObjectCache = appDelegate.managedObjectStore.managedObjectCache;
self.managedObjectContext = self.objectManager.managedObjectStore.mainQueueManagedObjectContext;
}
This is the code in [AppDelegate setupCoreDataWithRESTKit];
- (RKManagedObjectStore *)setupCoreDataWithRESTKit
{
NSError * error;
NSURL * modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"App" ofType:#"momd"]];
NSManagedObjectModel * managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
self.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
[self.managedObjectStore createPersistentStoreCoordinator];
NSArray * searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentPath = [searchPaths objectAtIndex:0];
NSPersistentStore * persistentStore = [self.managedObjectStore addSQLitePersistentStoreAtPath:[NSString stringWithFormat:#"%#/App%#.sqlite", documentPath, [Persistence username]] fromSeedDatabaseAtPath:nil withConfiguration:nil options:[self optionsForSqliteStore] error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
NSLog(#"Path: %#", [NSString stringWithFormat:#"%#/App%#.sqlite", documentPath, [Persistence username]]);
if(!persistentStore){
NSLog(#"Failed to add persistent store: %#", error);
}
[self.managedObjectStore createManagedObjectContexts];
return self.managedObjectStore;
}
Please note that each user has a different .sqlite file loaded based on their username: i.e. AppUserName. So when I logout and log back in if it's a same user, then the same file is created/loaded. If it's a different user, then a different name file is created/loaded.
Question: Why does NSFetchedResultsController displays an empty tableView after I logout and log back in, but it works fine when I launch the app the first time?
*EDIT *
I changed and tried the code below but the problem persists:
- (void)logoutWithCompletionBlock:(void(^)(void))completionBlock
{
[Persistence setLoggedInStatus:NO];
RKObjectManager *objectManager = [self getObjectManager];
[objectManager.HTTPClient clearAuthorizationHeader];
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = delegate.managedObjectStore.mainQueueManagedObjectContext;
[self clearManagedObjectContext:managedObjectContext];
[delegate deregisterWithUrbanAirship];
if (completionBlock)
{
completionBlock();
}
}
- (void)clearManagedObjectContext:(NSManagedObjectContext*)managedObjectContext
{
NSFetchRequest * fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"EntityA" inManagedObjectContext:managedObjectContext]];
NSMutableArray *result = [NSMutableArray arrayWithArray:[managedObjectContext executeFetchRequest:fetch error:nil]];
for (id entityA in result)
{
[managedObjectContext deleteObject:entityA];
}
[result removeAllObjects];
[fetch setEntity:[NSEntityDescription entityForName:#"EntityB" inManagedObjectContext:managedObjectContext]];
result = [NSMutableArray arrayWithArray:[managedObjectContext executeFetchRequest:fetch error:nil]];
for (id entityB in result)
{
[managedObjectContext deleteObject:entityB];
}
[result removeAllObjects];
[fetch setEntity:[NSEntityDescription entityForName:#"EntityC" inManagedObjectContext:managedObjectContext]];
result = [NSMutableArray arrayWithArray:[managedObjectContext executeFetchRequest:fetch error:nil]];
for (id entityC in result)
{
[managedObjectContext deleteObject:entityC];
}
[result removeAllObjects];
[managedObjectContext saveToPersistentStore:nil];
}
You shouldn't be doing [managedObjectContext reset]; unless you tear down the persistent store that is backing the main thread context (so, tear down the whole Core Data stack and destroy the SQLite file).
Either correct this or just loop over the things you want to delete in the context and save the changes up to the (parent) persistent context.

CoreData, UIManagedDocument and empty Persistent Store

I'm using UIManagedDocument to reading and writing to CoreData. I have Document class. This is a document from some tutorial:
.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
typedef void (^OnDocumentReady) (UIManagedDocument *document);
#interface Document : NSObject
#property (strong, nonatomic) UIManagedDocument *document;
+ (Document *)sharedDocument;
- (void)performWithDocument:(OnDocumentReady)onDocumentReady;
#end
.m
#interface Document ()
- (void)objectsDidChange:(NSNotification *)notification;
- (void)contextDidSave:(NSNotification *)notification;
#end;
#implementation Document
#synthesize document = _document;
static Document*_sharedInstance;
+ (Document *)sharedDocument
{
static dispatch_once_t once;
dispatch_once(&once, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Document"];
self.document = [[UIManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
self.document.persistentStoreOptions = options;
// Register for notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:self.document.managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:self.document.managedObjectContext];
}
return self;
}
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
onDocumentReady(self.document);
};
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
[self.document saveToURL:self.document.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateClosed) {
[self.document openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateNormal) {
OnDocumentDidLoad(YES);
}
}
- (void)objectsDidChange:(NSNotification *)notification
{
#ifdef DEBUG
NSLog(#"NSManagedObjects did change.");
#endif
}
- (void)contextDidSave:(NSNotification *)notification
{
#ifdef DEBUG
NSLog(#"NSManagedContext did save.");
#endif
}
#end
Then in ViewController i have NSURLConnection:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
DataParser *parser = [[DataParser alloc] init];
[parser startParsingData:self.myMutableData withContext:self.moc];
}
and i open the document here:
-(void)initDocument {
if (!self.moc) {
[[Document sharedDocument] performWithDocument:^(UIManagedDocument *document) {
self.moc = document.managedObjectContext;
[[NSNotificationCenter defaultCenter] postNotificationName:UIDocumentStateChangedNotification object:self];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"someURL"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
[NSURLConnection connectionWithRequest:request delegate:self];
}];
}
}
Then i try to parse data:
-(void)startParsingData:(NSData*)data withContext:(NSManagedObjectContext*)context {
self.moc = context;
self.startDate = [NSDate date];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
}
ant try to load it in core data after XML parsing:
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if([elementName isEqualToString:#"Item"]) {
[self.moc performBlockAndWait:^{
TestEntity *te = [NSEntityDescription insertNewObjectForEntityForName:#"TestEntity" inManagedObjectContext:self.moc];
te.surname = #"5433fds";
te.name = #"5342fdsfsd";
}];
}
}
And i see the logs:
2014-01-24 16:21:21.692 Ce[85149:70b] NSManagedObjects did change.
2014-01-24 16:21:36.696 Ce[85149:70b] NSManagedContext did save.
So i assume that this should be in sqlite file, but when I try to read it is completely empty. awl file have some bigger size, but persistentStore have 0 rows. Why?
What happens when you launch the app a second time, can you see the data in your app?
In iOS 7, SQLite now has journaling turned on. I wonder if the journalling is causing a confusion (data is there but not in the sqlite file yet).
I would also question why you are using a UIManagedDocument. It appears you are using Core Data in a Singleton pattern (which is bad) and then using UIManagedDocument in that singleton pattern (worse).
UIManagedDocument is intended for document based apps. You are probably running foul of some of the "automated" features inside of that class that would be easily cleared up if you built a standard Core Data stack instead.
You might consider turning Journaling off, adding a vacuum option to your NSPersistentStoreCoordinator or switch to a standard Core Data stack.
This is likely because of a new SQLite journal mode used in iOS 7. If you set it to the old mode as shown in the answer to the following SO question you should see your data again.
How to disable WAL journal mode

Could not create UIManagedDocument

I'm trying to make an iPhone app using Core Data. I have to use NSManagedObjectContext to access data and in order to do this i use UIManagedDocument. But if I try to create a document with UIManagedDocument, document's openWithCompletionHandler isn't success. This why my NSManagedObjectContext is always nil and Xcode says could not create document at etc. Here are the classes:
AddUserViewController.h
#import <UIKit/UIKit.h>
#interface AddUserViewController : UIViewController
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property (nonatomic,strong) UIManagedDocument *document;
#end
AddUserViewController.m
#import "AddUserViewController.h"
#import "User.h"
#import "User+Create.h"
#interface AddUserViewController ()
#property (weak, nonatomic) IBOutlet UITextField *nameField;
#property (weak, nonatomic) IBOutlet UITextField *ageField;
#property (weak, nonatomic) IBOutlet UITextField *sexField;
#property (weak, nonatomic) IBOutlet UITextField *weightField;
#property (weak, nonatomic) IBOutlet UITextField *activityField;
#end
#implementation AddUserViewController
-(void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext{
_managedObjectContext = managedObjectContext;
}
-(void)createOrWriteDocument{
NSURL *url = [[[NSFileManager defaultManager]URLsForDirectory:NSDocumentationDirectory inDomains:NSUserDomainMask]firstObject];
url = [url URLByAppendingPathComponent:#"Activities"]; // edited mistakenly deleted
self.document = [[UIManagedDocument alloc] initWithFileURL:url];
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[self.document openWithCompletionHandler:^(BOOL success) {
if (success) [self documentIsReady];
if (!success){
NSLog(#"could not open document at %#",url);
}
}];
} else {
[self.document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
[self documentIsReady];
}
if (!success){
NSLog(#"could not create document at %#",url);
}
}];
}
}
- (void)documentIsReady
{
if (self.document.documentState == UIDocumentStateNormal) {
self.managedObjectContext = self.document.managedObjectContext;
} }
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
if (!self.managedObjectContext) {
[self createOrWriteDocument];
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
}
-(void)dismissKeyboard{
[self.nameField resignFirstResponder];
[self.ageField resignFirstResponder];
[self.sexField resignFirstResponder];
[self.weightField resignFirstResponder];
[self.activityField resignFirstResponder];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"setUser:"]) {
//User *user = [[User alloc]init];
User *user = [User createUserWithname:self.nameField.text
withAge:[NSNumber numberWithDouble:[self.ageField.text doubleValue]]
withSex:self.sexField.text
withWeight:[NSNumber numberWithDouble:[self.weightField.text doubleValue]]
withActivity:self.activityField.text
inManagedObjectContext:self.managedObjectContext];
if ([segue.destinationViewController respondsToSelector:#selector(setUser:)]) {
[segue.destinationViewController performSelector:#selector(setUser:) withObject:user];
}
}
}
#end
EDIT
I solved the problem.Problem is at the NSURL *url = [[[NSFileManager defaultManager]URLsForDirectory:NSDocumentationDirectory inDomains:NSUserDomainMask]firstObject];
line. Instead of NSDocumentationDirectory, I used NSDocumentDirectory and it solved the problem.
You can try this code to create you managedobjectcontext.
- (NSManagedObjectContext *) managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return _managedObjectContext;
}
Take a look at the example below. This works for me, sorry don't have time to take a closer look to see the differences, I am using iCloud options so remove them but try setting the other persistentStoreOptions. And also post details of the error are you getting?
// This gets called when the user has done one of the following:
// 1. Created a new file and entered a new file name. We have then created the fileURL
// using the /Documents directory, the filename and appending '_UUID_'+uuid to ensure that
// avoid duplicate file names in case the user used the same file name on another device.
// 2. Selected an existing file from the file browser
//
- (void)createNewFile:(NSURL*)fileURL {
//FLOG(#"createNewFile called with url %#", fileURL);
_creatingNewFile = YES; // Ignore any file metadata scan events coming in while we do this because some iCloud
// files get created by Core Data before the local files are created and our scanning
// picks up new iCloud files and attempts to create local copies and we don't want this
// if this devices is busy creating the new iCloud file
_document = [[OSManagedDocument alloc] initWithFileURL:fileURL];
// Set oberving on this file to monitor the state (we don't use it for anything other than debugging)
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(documentStateChanged:)
name:UIDocumentStateChangedNotification
object:_document];
_openedVersion = [NSFileVersion currentVersionOfItemAtURL:fileURL];
_openedVersionDate = _openedVersion.modificationDate;
_openedVersionDevice = _openedVersion.localizedNameOfSavingComputer;
//FLOG(#" file version date: %#", _openedVersionDate);
//FLOG(#" file version device: %#", _openedVersionDevice);
NSString *fileName = [[fileURL URLByDeletingPathExtension] lastPathComponent];
[_document setPersistentStoreOptions:#{NSPersistentStoreUbiquitousContentNameKey:fileName,
NSMigratePersistentStoresAutomaticallyOption:#YES,
NSInferMappingModelAutomaticallyOption:#YES,
NSSQLitePragmasOption:#{ #"journal_mode" : #"DELETE" }}];
_managedObjectContext = _document.managedObjectContext;
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
//FLOG(#" file exists so open it: %#",fileURL);
[_document openWithCompletionHandler:^(BOOL success){
if (!success) {
// Handle the error.
LOG(#" error creating file");
} else {
//LOG(#" file opened");
[self fileOpened]; // Now initialise the UI and let the user continue...
}
}];
}
else {
// File does not exist so that means the user has created a new one and we need to
// load some initialisation data into the Core Data store (codes tables, etc.)
//
// At this stage we have a database in memory so we can just use the _document.managedObjectContext
// to add objects prior to attempting to write to disk.
//LOG(#" file DOES NOT exist so add initial data");
[self addInitialData];
// Just checking if anything has been written to disk, nothing should not exist on disk yet.
// Debugging use only
if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
LOG(#" file exists but keep going anyway :-(");
else
LOG(#" file still does not exist :-)");
// OK now save a copy to disk using UIManagedDocument
// NOTE: the iCloud files are written before the UIManagedDocument.fileURL, presumably because Core Data does this setup
// in response to the [moc save:]. Make sure we don't pick this up in our iCloud metaData scan and attempt to create
// it as if it were a new iCloud file created by some other device.
//
[_document saveToURL:_document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if (!success) {
// Handle the error.
LOG(#" error saving file :-(");
}
// We close the file and wait for it to appear in the file browser and then
// let the user select it from the browser to open it and start using it.
// Skip this if you want to open it directly and [self fileopened] but
// bear in mind the fileListController will not be correctly set up when we return to it.
[_document closeWithCompletionHandler:^(BOOL success){
if (!success) {
// Handle the error.
LOG(#" error closing file after creation :-(");
FLOG(#" file URL is %#", [fileURL path]);
} else {
FLOG(#" file closed %#", fileName);
_creatingNewFile = NO; // OK we are done, so let metaData scanning go ahead as normal
// Tell our UITableView file list that we are done and trigger scanning of local and iCloud files
// The fileListController will the add the new file itself and the user will then pick the
// file from this list in order to open it.
// To open the file automatically use the callback in the fileListController
// to select and then open the file so it looks seamless to the user.
[self.fileListController fileHasBeenCreated:fileURL];
// Stop observing now
[center removeObserver:self
name:UIDocumentStateChangedNotification
object:_document];
}
}];
}];
}
}
Oh and also create a subclass of UIManagedDocument so you can get the errors, if you don't already have one. All you need is the following in the subclass.
#implementation OSManagedDocument
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
LOG(#"Auto-Saving Document");
return [super contentsForType:typeName error:outError];
}
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted
{
FLOG(#" error: %#", error.localizedDescription);
NSArray* errors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(errors != nil && errors.count > 0) {
for (NSError *error in errors) {
FLOG(#" Error: %#", error.userInfo);
}
} else {
FLOG(#" error.userInfo = %#", error.userInfo);
}
}
#end
This is the method to get the /Documents directory on the device. The file name is appended to this path.
- (NSURL*)documentsDirectoryURL
{
_dataDirectoryURL = [NSURL fileURLWithPath:NSHomeDirectory() isDirectory:YES];
return [_dataDirectoryURL URLByAppendingPathComponent:#"Documents"];
}
Here is a link to more information http://ossh.com.au/design-and-technology/software-development/uimanageddocument-icloud-integration/

iCloud Save data With UIDocument crash

I am trying to use iCloud to store my app's userSetting, here is my Save & Load Code: , it usually doing fine but sometimes crash with message like: attempt to open or a revert document that already has an open or revert operation in flight or send to dealloc instance so i add fileState logs befere openWithCompletionHandler it always show state = UIDocumentStateClosed no matter will crash or not, i save data when applecationDidEnterBackground and load when applicationDidBecomeActive.
save:
-(void)storeToiCloud{
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL) {
NSURL *documentsURL = [baseURL URLByAppendingPathComponent:#"Documents"];
NSURL *documentURL = [documentsURL URLByAppendingPathComponent:[NSString stringWithFormat:#"userSetting"]];
if (!loadDocument) {
self.loadDocument = [[MyUserDefaultsDocument alloc] initWithFileURL:documentURL];
}
loadDocument.myUserDefault = [MyUserDefaults standardUserDefaults];
[loadDocument saveToURL:documentURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
}];
}
}
load:
-(BOOL)shouldSynciCloud{
if (![Utility iCloudEnable]) {
return NO;
}
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL) {
self.query = [[[NSMetadataQuery alloc] init] autorelease];
[self.query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == 'userSetting'", NSMetadataItemFSNameKey];
[self.query setPredicate:predicate];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(queryDidFinish:) name:NSMetadataQueryDidFinishGatheringNotification object:self.query];
[self.query startQuery];
[Utility showSpinner];
return YES;
}
return NO;
}
- (void)queryDidFinish:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
// Stop Updates
[query disableUpdates];
// Stop Query
[query stopQuery];
[query.results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSURL *documentURL = [(NSMetadataItem *)obj valueForAttribute:NSMetadataItemURLKey];
if([[documentURL lastPathComponent] hasPrefix:#"userSetting"]){
self.document = [[MyUserDefaultsDocument alloc] initWithFileURL:documentURL];
NSString* message;
if (document.documentState == UIDocumentStateNormal){
message = #"UIDocumentStateNormal";
}else if (document.documentState == UIDocumentStateClosed) {
message = #"UIDocumentStateClosed";
}else if(document.documentState == UIDocumentStateEditingDisabled){
message = #"UIDocumentStateEditingDisabled";
}else if(document.documentState == UIDocumentStateInConflict){
message = #"UIDocumentStateInConflict";
}else if(document.documentState == UIDocumentStateSavingError){
message = #"UIDocumentStateSavingError";
}
NSLog(#"state = %#",message);
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
MyUserDefaults *prefs = [MyUserDefaults standardUserDefaults];
NSData *book =[document.myUserDefault.realDict objectForKey:#"realbook"];
NSData *readSetting = [document.myUserDefault.realDict objectForKey:#"epubRS"];
if (book&&[[NSUserDefaults standardUserDefaults] boolForKey:#"iCloudBook"]) {
[prefs setObject:book forKey:#"realbook"];
[Utility reloadRealBooks];
}
if (readSetting&&[[NSUserDefaults standardUserDefaults] boolForKey:#"iCloudSetting"]) {
[prefs setObject:readSetting forKey:#"epubRS"];
[Utility setEpubReadSettingFromData:readSetting];
}
[prefs save];
[[NSNotificationCenter defaultCenter]postNotificationName:#"iCloudSynced" object:nil];
[Utility removeSpinner];
}
else{
[[NSNotificationCenter defaultCenter]postNotificationName:#"iCloudSyncfailed" object:nil];
[Utility removeSpinner];
}
}];
}
}];
if ([query.results count]==0) {
[[NSNotificationCenter defaultCenter]postNotificationName:#"iCloudSyncfailed" object:nil];
[Utility removeSpinner];
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:nil];
}
As noted in this question, the error occurs if your app attempts to call [document openWithCompletionHandler:] method twice in close succession.
Because the openWithCompletionHandler: opens the document asynchronously, the document may still be opening when the method is called again.
If this happens, your app ends up trying to open the document twice (as the document state will remain UIDocumentStateClosed until completion) and this causes the exception to be thrown.

Resources