I'm (sort of) following the Stanford CS193P ios class and I'm trying to get a document context without having to pass from controller to controller. Prof Haggerty uses this method to get his context, but it doesn't work for me. I'm spelling everything correctly and I can get the context when I pass it, but not when I get it this way.
Am I missing something?? I just want to get the context for the database that I know I've created without having to pass in.s
- (void)useDemoDocument
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Demo Document"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
[self refresh];
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
self.managedObjectContext = document.managedObjectContext;
}
}
I don't think you misspelled anything, but you might have the wrong expectation of what that method is doing. The method is not returning any context. It sets the context as a class property (but only in some cases!).
So after calling this method you should be able to access the self.managedObjectContext property.
However.. The method implementation is not very clean and therefore a bit dangerous. It will set the 'managedObjectContext' property only for successful scenarios. The method does not clear the self.managedObjectContext property in other cases, so in those cases it's unclear what the self.managedObjectContext is pointing to.
Since the method does not provide any success status you can never trust if the managedObjectContext has been set properly.
I had the same problem (also doing Stanford class). I've since found the solution, you can find it here iOS 7 Completion handler never gets called
OpenWithCompletionHandler is asynchronous, so the application might try to use the context even before completion handler block finishes executing. Hence, it seems like the method doesn't work. Using a run loop will solve your problem.
Related
I have a simple app that i'm building to consolidate my little knowledge about core data. My main problem is that my method for get the NSManagedObjectContext returns nil. The way to get this is showed in one of the Stanford classes about core data. I'm doing something wrong, but i don't know what(My code looks identical with the one showed in class)... Of course, the results are a crash in my app, that complains about "+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name...", when i try to save the data, but my context is nil.
The code to get the NSManagedObjectContext:
- (void)setupFile {
NSURL *URL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentationDirectory
inDomains:NSUserDomainMask] lastObject];
URL = [URL URLByAppendingPathComponent:#"FlashCardCoreData"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:URL];
if ([[NSFileManager defaultManager] fileExistsAtPath:[URL path]] == NO) {
[document saveToURL:URL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success) {
self.sharedContext = document.managedObjectContext;
}
}];
} else if (document.documentState == UIDocumentStateClosed) {
// open the document
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.sharedContext = document.managedObjectContext;
}
}];
} else {
self.sharedContext = document.managedObjectContext;
}
}
This class is a singleton class for share a global context that i can use in all of the controllers and forms for displaying/saving the objects...
Note: I didn't used the global context shared in the app delegate because i did't choose my app for use the core data(hence it's not an available property in the delegate), and i don't know how can i change that once my app is already created.
Can anyone help me? Any suggest will be appreciated.
Thanks for the attention.
The easiest way to see the correct way to implement CoreData is to simply create a new project and check the "Use Core Data" check box when setting up options for the project.
This will inject all of the boiler plate code that you need for initializing core data. You can then copy it into your singleton class.
You then need to make your Managed Object Data Model.
Once the Data Model is fully configured, simply create an NSManagedObject subclass for the Data Model. This will automatically pull all of the properties/relationships from the Data Model file.
Here is a great tutorial for setting up and implementing Core Data.
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.
I am kinda new to Core Data, and have encountered some wierd stuff when making a point counter for a game.
I have this code for creating a new Point-object for a match (the static holder "button.pointHolder" is not a core data-object):
NSLog(#"before block");
[self.match.managedObjectContext performBlock:^{
NSLog(#"in block");
PointHolderType *holderType = [self.matchController insertPointHolderForTeam:self.match.homeTeam withStaticHolder:button.pointHolder];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"in block dispatch_get_main_queue");
//Updating UI
});
}];
NSLog(#"after block");
This seems to block the main thread when my core data is growing. (It doesn't matter if its a new match instance without point holders or not. It is lagging when my core data is growing no matter what)
The logs:
2013-11-04 11:48:53.059 [463:60b] before block
2013-11-04 11:48:53.060 [463:60b] after block
2013-11-04 11:48:53.062 [463:60b] in block
2013-11-04 11:48:53.606 [463:60b] in block dispatch_get_main_queue
And I'm creating my object like this:
- (PointHolderType *)insertPointHolderForTeam:(NSString*)team withStaticHolder:(PointHolder *)holder
{
PointHolderType *holderType = [NSEntityDescription insertNewObjectForEntityForName:#"PointHolderType" inManagedObjectContext:self.match.managedObjectContext];
holderType.type = [NSNumber numberWithInt:holder.pointCounterType];
holderType.team = team;
holderType.points = [NSNumber numberWithInteger:holder.pointsAdded];
holderType.match = self.match;
return holderType;
}
Also, if i remove this line:
holderType.match = self.match;
I get a fully responsive UI. This makes me wonder if it has something to do with my managedObjectContext which I'm using from the "match" itself.
Other info:
I'm creating my match like this:
Match *newMatch = [NSEntityDescription insertNewObjectForEntityForName:#"Match" inManagedObjectContext:self.managedObjectContext];
And my managedObjectContext like this:
-(void) useDocument
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"brannboll_document"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if(![[NSFileManager defaultManager] fileExistsAtPath:url.path])
{
//create
[document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if(success)
{
self.managedObjectContext = document.managedObjectContext;
}
}];
}
else if(document.documentState == UIDocumentStateClosed)
{
//Open it
[document openWithCompletionHandler:^(BOOL success) {
self.managedObjectContext = document.managedObjectContext;
}];
}
else //try to use it
{
self.managedObjectContext = document.managedObjectContext;
}
}
Any ideas? What am I missing?
EDIT:
Thanks to CouchDeveloper in the comments, I managed to find the root of the problems; My parent Core Data Table View controller, whose fetchrequest got constantly called when adding relations to a Match-object. Why? You tell me. I solved it by nil-ing the actual fetchrequest in viewdiddisappear, and vice verca.
Thanks!
EDIT: based on the (very helpful) comment below, after further review, I'd just like to add (for other rookies like myself) that when using UIManagedDocument to obtain your core data functionality, pay attention to WHEN your instance is being opened in your app.
The error I was getting was because my managed object context WAS nil when my fetched results controller was being set up. I moved the method call which set up the fetched results controller to after the UIManagedDocument instance was opened.
It might be very basic, and just a common-sense issue to most, but for us rookies, when core data is not being set up in the app delegate, we need to learn that the document has to be in a usable state before we can set the fetchedResultsController.
I'm working through Paul Hegarty's Stanford lectures on Core Data, specifically the demo lecture for the "Photomania" app.
Instead of using NSDictionaries of photos, I modified the app to include only Model objects that store NSStrings (for example, a person's name, etc.) It's just a learning exercise for me.
I believe I've successfully recreated a UIManagedDocument using his code, and set the view controller's managed object property to that of the document's managed object context through the two methods below (they're his methods, not mine, of course).
I put this code in a tableview controller that will appear on screen so I could test in viewDidLoad if the managed object context exists (it's a subclass of his CoreDataTableViewController class). Is that even a valid test for a managed object context?
I understand the code that creates or opens the UIManagedDocument, but I don't understand why that managed object context is nil (if i modify the little test in viewDidLoad, it will tell me that the context is == nil ).
At this point, nothing has been written to the context, I haven't included any fetched results either.
I'm breaking it down into sections because my simplified version of the lecture app kept giving me the error that the managed object context and fetched results controller were nil.
I want to test, in this case, if I'm getting a valid managed object context before moving forward. I have a strong feeling there's some fundamental piece of info I'm missing in what's essentially copied code (for learning purposes).
Does anyone know why the managed object context is nil? Or SHOULD it be that way at this point because I've missed something when setting it? Or when I set the UIManagedDocument's context as the table view controller's ( self) context?
Any information would be greatly appreciated.
-(void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
_managedObjectContext = managedObjectContext;
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.managedObjectContext) [self useDemoDocument];
}
-(void)useDemoDocument
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Demo Document"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
//create it
[ document saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else if (document.documentState == UIDocumentStateClosed){
//open it
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
self.managedObjectContext = document.managedObjectContext;
}
}];
} else {
//try to use it
self.managedObjectContext = document.managedObjectContext;
}
}
-(void)viewDidLoad
{
if (self.managedObjectContext) {
NSLog(#"there is a managed object context");
}
}
I have a singleton class (DTTSingleton) with the following methods:
+ (UIManagedDocument *)managedDocument
{
static UIManagedDocument *managedDocument = nil;
static dispatch_once_t mngddoc;
dispatch_once(&mngddoc, ^
{
if(!managedDocument)
{
NSURL *url = [[DTTHelper applicationDocumentsDirectory] URLByAppendingPathComponent:kDTTDatabaseName];
managedDocument = [[DTTManagedDocument alloc] initWithFileURL:url];
}
});
return managedDocument;
}
+ (void)useDefaultDocumentWithBlock:(completion_block_t)completionBlock
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[DTTSingleton.managedDocument.fileURL path]])
{
[DTTSingleton.managedDocument saveToURL:DTTSingleton.managedDocument.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
{
if (success)
{
completionBlock(DTTSingleton.managedDocument.managedObjectContext);
}
else
{
NSLog(#"Failed to save!");
}
}];
}
else if (DTTSingleton.managedDocument.documentState == UIDocumentStateClosed)
{
[DTTSingleton.managedDocument openWithCompletionHandler:^(BOOL success)
{
if (success)
{
completionBlock(DTTSingleton.managedDocument.managedObjectContext);
}
else
{
NSLog(#"Failed to open!");
}
}];
}
else if (DTTSingleton.managedDocument.documentState == UIDocumentStateNormal)
{
completionBlock(DTTSingleton.managedDocument.managedObjectContext);
}
}
And in my UITableViewController I have the following code in the viewDidLoad method:
[DTTSingleton useDefaultDocumentWithBlock:^(NSManagedObjectContext *moc)
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"SomeEntity"];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc cacheName:nil];
}];
[DTTSingleton useDefaultDocumentWithBlock:^(NSManagedObjectContext *moc)
{
NSLog(#"When this is called it errors because DTTSingleton is already trying to open it!");
}];
When executed I get the error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'attempt to open or a
revert document that already has an open or revert operation in flight
I understand why I'm getting this error, it's because I'm trying to open the document when another opening process is already running. So my question are...
1) How do I ensure openWithCompletionHandler is only called once?
2) How do I ensure the second block is executed once the document has opened?
Thanks for any help!
I'm not sure if you've seen this yet, but a good resource would probably be here: http://adevelopingstory.com/blog/2012/03/core-data-with-a-single-shared-uimanageddocument.html
In the event that you're just trying to create your own (rather than using the above link - or similar) and are looking for some input, I can point out a few things I see (though I do not claim by any means to be an expert)...
Anyways, I believe your issue stems here:
// ....
} else if(DTTSingleton.managedDocument.documentState == UIDocumentStateClosed) {
[DTTSingleton.managedDocument openWithCompletionHandler:^(BOOL success) {
if(success) {
completionBlock(DTTSingleton.managedDocument.managedObjectContext);
} else {
NSLog(#"Failed to open!");
}
}];
}
The method openWithCompletionHandler attempts to open a connection to the document asynchronously. The issue with this is that, on your first call to open the document in your UITableView, the code you're using notices the document is closed - so it attempts to open it. This is all fine and dandy, but the code you're using here then re-issues yet another attempt to create an instance of the singleton. More than likely, this is happening so fast (and close together) that it, yet again, attempts to open the document asynchronously.
To test this, try putting a breakpoint after the UIDocumentStateClosed check for the line:
[DTTSingleton.managedDocument openWithCompletionHandler:^(BOOL success)
I believe you'll see this being executed numerous times...
I'm not skilled enough to explain how to solve this, but I would seriously recommend using the approach shown in the link above where he applies a block to assign/track the existence of an open document
Hopefully that helps?
Edit:
* Added the suggestion for the breakpoint.
Edit: Here is another stackoverflow ticket with a similar issue (and suggestion/conclusion) - so I may not be too far off here after all =)
Just thought I'd post back as say the issues I was having have been solved by disabling buttons that access Core Data until the document is ready, this way you'll never try to open the document at the same time as another process. And as for Core Data access in a life cycle handler like viewDidLoad, I implemented a system where by if the document is opening (state kept manually with a variable) it delays the call by looping until the document is open, in essence queuing the calls. Don't forget to use performSelector:withObject:afterDelay: in the call within to loop otherwise you'll get an application crash.
Thanks for your suggestions John.