I am using Justin Driscoll's implementaion on Core Data with a Single Shared UIManagedDocument. Everything was fine in my iphone app until I moved it to a iPad storyboard and a splitview controller for the ipad app. The problem is openwithCompletionHandler is being called twice, once from my master view in viewDidLoad and again in my detail view viewWillLoad. The calls are in quick succession and since the document is still in UIDocumentStateClosed when the second call is made to my performWithDocument method (below) of the singleton the app crashes. I looked at e_x_p ' s answer for post iOS5.1: synchronising tasks (wait for a completion) but @sychronized will not work in this case since performWithDocument below is called on the same thread. How would I protect against multiple calls to openwithCompletionHandler? The only way I can think to protect against this is to pause execution of one of the calls above until i am sure UIDocumentStateNormal is true and then release. That though would freeze the main UI thread which is not good. What though would be the best way todo this without freezing up the UI?
From the UIManagedDocumentSingleton code:
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success)
{
onDocumentReady(self.document);
};
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]])
{
//This should never happen*******************
[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);
}
}
The block of code that is shared between
numberOfRowsInSection:
andcellForRowAtIndexPath:
should be called only once.numberOfRowsInSection
will always be called before thetableView
tries to render the cells, so you should create anNSArray
object that you can store the results of the fetch request into, and then use this array when rendering your cells:I'm not sure off the top of my head if you need to do something special with the
NSArray
to set it within the block (a la__block
).The major reason for this is that you need to make sure that 100% of the time the dataset used to determine the # of rows is the same size as when you are creating your cells. If they don't match then you will crash. Also since you don't have a block you don't need to dispatch to make
UITableViewCell
updates now.Finally if
UIDocumentStateClosed
is causing problems you should either filter them out of yourNSFetch
results (additional predicate, seeNSCompoundPredicate
if required) or have code to handle them better incellForRowAtIndexPath:
I did it as Justin suggested
abovebelow. Works fine in one of my apps for two years with ~20k users.Swift (not much tested)
That's interesting and definitely a flaw in my code (sorry!). My first thought would be to add a serial queue as a property to your document handler class and perform the check on that.
and then in performWithDocument:
But that wouldn't work either...
You could set a BOOL flag when you call saveToURL and clear it in the callback. Then you can check for that flag and use performSelectorAfterDelay to call performWithDocument again a little later if the file is being created.