CocoaPod with category not available at runtime - ios

I know the same kind of issue has already been posted, but I think I face another one.
I created the CocoaPod called NSURLSession+PromiseKit to allow using PromiseKit with NSURLSession.
I have the definition:
#implementation NSURLSession (PromiseKit)
- (PMKPromise *)promiseDataTaskWithURL:(NSURL *)url {
return [PMKPromise ...];
}
#end
And the code sample:
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:#"..."];
[session promiseDataTaskWithURL:url].then( ^(NSData *data) {
NSLog(#"Result: %#", data);
}).catch( ^(NSError *e) {
NSLog(#"Error: %#", e);
});
works on iOS 8, but not on iOS 7. I get this error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFURLSession promiseDataTaskWithURL:]: unrecognized selector sent to instance 0x7c236720'
Now of course I though that the category was not loaded so I added this line to the podspec to make sure that the code is loaded:
s.xcconfig = { 'OTHER_LDFLAGS' => '-ObjC -all_load' }
But it does not work better.
So I added a line to the category file:
+ (void)load {
NSLog(#"loaded");
}
To my surprise the load method is called, which means that the code is indeed loaded. So why is the category method not found while executing if the code is loaded?

Ok, as often, posting the question lead me to the answer.
It has been found that NSURLSession does not support categories.
In iOS 7, the underlying NSURLSession class is __NSCFURLSession, which does not support the categories.
In iOS 8, the underlying class is __NSURLSessionLocal, which does support categories.
My solution, although cringeworthy is to replace:
#implementation NSURLSession (PromiseKit)
By:
#implementation NSObject (PromiseKit)
Only on the implementation file. This will prevent a Pod user to mistakenly use the method with something else than a NSURLSession object. However, it does not prevent him to play other tricks to make the thing crash...

Related

iOS Video Caching - Manual cache deletion

I have a React Native application which uses React Native Video with iOS caching. I have been working on a method inside RCTVideoCache.m which would manually delete the data of a particular cache key. According to the documentation of SPTPersistentCache, which the video library uses for caching, data can be deleted either by locking/unlocking a file and invoking a wipe or after inspecting the source code of SPTPersistentCache.h with a method named removeDataForKeys.
I have tried both ways, however, unsuccessfully.
In my first try, I am using wipeLockedFiles. I have created a deleteFromCache() method inside RCTVideoCache.m. Since all my video files are unlocked by default, in this method I am trying to lock the file corresponding to my cacheKey and invoke a wipe on all locked files (which would consist of only my target cacheKey file) as it is demonstrated in the documentation. This method looks like:
- (void)deleteFromCache:(NSString *)cacheKey withCallback:(void(^)(BOOL))handler;
{
[self.videoCache lockDataForKeys:#[cacheKey] callback:nil queue:nil];
[self.videoCache wipeLockedFiles];
NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes));
handler(YES);
}
The following results in two errors during compilation:
/Users/.../MyApp/node_modules/react-native-video/ios/VideoCaching/RCTVideoCache.m:79:20: error: no visible #interface for 'SPTPersistentCache' declares the selector 'lockDataForKeys:callback:queue:'
[self.videoCache lockDataForKeys:#[cacheKey] callback:nil queue:nil];
~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/.../MyApp/node_modules/react-native-video/ios/VideoCaching/RCTVideoCache.m:80:20: error: no visible #interface for 'SPTPersistentCache' declares the selector 'wipeLockedFiles'
[self.videoCache wipeLockedFiles];
~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~
I really have no idea why these selectors are not visible from SPTPersistentCache.
In my second try, I am using removeDataForKeys(). Again, I have created a deleteFromCache() method inside RCTVideoCache.m which looks like this:
- (void)deleteFromCache:(NSString *)cacheKey withCallback:(void(^)(BOOL))handler;
{
[self.videoCache removeDataForKeys:#[cacheKey] callback:^(SPTPersistentCacheResponse * _Nonnull response) {
NSLog(#"Result output: %#", response.output);
NSLog(#"Error output: %#", [response.error localizedDescription]);
} onQueue:dispatch_get_main_queue()];
NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes));
handler(YES);
}
In this second way, there are no errors, however, the data of the key is never deleted. Also, both NSLogs for the response output null inside the terminal.
I am 100% sure that the cacheKey I am providing to my deleteFromCache() method is correct and data corresponding to it exists. However, in both methods NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes)); does not change and I can also manually verify that the file has not been deleted.
I am really stuck and do not know what is wrong with the code I've written in both cases and why neither of them works. I would appreciate any help on this!
You can delete all sub-folder's files (tmp/rct.video.cache), iterating each one:
+ (void)deleteFromCache
{
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.temporaryCachePath error:NULL];
for (NSString *file in tmpDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#%#", self.temporaryCachePath, file] error:NULL];
}
}
I ran your example and discovered that you are using incorrect method signatures. These methods simply don't exist in the caching library, their signatures are different.
Try something like this:
- (void)deleteFromCache:(NSString *)cacheKey withCallback:(void(^)(BOOL))handler;
{
NSLog(#"Size before = %#", #(self.videoCache.totalUsedSizeInBytes));
[self.videoCache lockDataForKeys:#[cacheKey] callback:nil onQueue:nil];
[self.videoCache wipeLockedFilesWithCallback:^(SPTPersistentCacheResponse * _Nonnull response) {
NSLog(#"Size after = %#, response = %#", #(self.videoCache.totalUsedSizeInBytes), response);
// Call handler after the files are wiped
handler(YES);
} onQueue:nil];
}
I have no idea why the second approach doesn't work, but NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes)); is for sure called before the actual deletion happens. In the example I posted above, I have moved the logging statement into the callback closure, so that it reports the size before and after the deletion takes place.

"NSURLSession sharedSession" is not kind of NSURLSession

I created category for NSURLSession, and then I faced a problem. In iOS 7, [[NSURLSession sharedSession] isKindOfClass:[NSURLSession class]] returns NO.
I know that [NSURLSession sharedSession] is returning instance of __NSCFURLSession, but it is not a subclass of NSURLSession. So, it doesn't make any sense. At compile time everyting is ok, but in runtime I get unrecognized selector exception.
How can I get around with this? Is there is any runtime magic feature, that I can use? Because
[[NSURLSession sharedSession] respondsToSelector:#selector(dataTaskWithRequest:completionHandler:)]
returns YES, and that is the only method I use in my category.
As an alternative to creating an instance method in your category you could create a class method which accepts an instance of NSURLSession.
Assuming a category like this:
#interface NSURLSession (MyCategory)
- (void)doSomethingWithCompletion:(MyCompletionHandler)completionHandler;
#end
The replacement could be:
#interface NSURLSession (MyCategory)
+ (void)doSomethingWithSession:(NSURLSession *)session completion:(MyCompletionHandler)completionHandler;
#end
With usage as follows:
[NSURLSession doSomethingWithSession:[NSURLSession sharedSession]
completion:^{ ... }];

Share Extension loadItemForTypeIdentifier returns error for NSURL

I have the following code to read in passed URLs. I'm testing this with the Pocket app and although hasItemConformingToTypeIdentifier is returning YES for kUTTypeURL, trying to load it in returns a error instead stating
"Unexpected value class."
. If I try to load it as an id<NSSecureCoding> item and debug, I find that the passed in object is indeed just the title of the page and not the URL. How do I read the URL?
NSURL *pageURL = nil;
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier: (NSString*) kUTTypeURL]) {
[itemProvider loadItemForTypeIdentifier:(NSString*) kUTTypeURL options:nil completionHandler:^(id <NSSecureCoding> urlItem, NSError *error) {
if ([((NSObject*)urlItem) isKindOfClass: [NSURL class]]) {
pageURL = [((NSURL*)urlItem) absoluteString];
}
}];
}
}
}
If you read the documentation for:
loadItemForTypeIdentifier(_:options:completionHandler:)
You'll see that:
The type information for the first parameter of your completionHandler
block should be set to the class of the expected type. For example,
when requesting text data, you might set the type of the first
parameter to NSString or NSAttributedString. An item provider can
perform simple type conversions of the data to the class you specify,
such as from NSURL to NSData or NSFileWrapper, or from NSData to
UIImage (in iOS) or NSImage (in OS X). If the data could not be
retrieved or coerced to the specified class, an error is passed to the
completion block’s.
Maybe you can experiment by coercing to different types?
Try this
__block NSURL *pageURL = nil;
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier: (NSString*) kUTTypeURL]) {
[itemProvider loadItemForTypeIdentifier:(NSString*) kUTTypeURL options:nil completionHandler:^(NSURL *urlItem, NSError *error) {
if (urlItem) {
pageURL = urlItem;
}
}];
}
}
}
And now if you want take URL of your current site use
NSString *output = [pageURL absolutestring];
Output - will be your URL.
I stumbled over this issue now myself. The Pocket App seems to be the only App which shows this issue. The strange thing is that there are Apps which can get the URL form Pocket. Like for example Firefox for iOS. Firefox is Open Source so I looked at its code (at Github) and found that it is doing exactly the same to get the URL that is shown here. The only difference is that Firefox is written in Swift, and my code (and the one posted here) is Objective C. So I wonder if the Pocket App is doing something strange that is triggering a bug in the Objective C API of the iOS only, so Swift Apps are not affected? I do not have any Swift experience yet, so I haven’t checked if switching to Swift would „solve“ this.
The fact that the method "hasItemConformingToTypeIdentifier:“ states that there is a URL available but „loadItemForTypeIdentifier:“ can not deliver it, is a clear indication that the iOS itself has a bug here (at least in the Objective C API). But there still must be something special the Pocket App is doing to trigger this bug, because otherwise this would not work in all other Apps.

AFNetworking and background transfers

I'm a bit confuse of how to take advantage of the new iOS 7 NSURLSession background transfers features and AFNetworking (versions 2 and 3).
I saw the WWDC 705 - What’s New in Foundation Networking session, and they demonstrated background download that continues after the app terminated or even crashes.
This is done using the new API application:handleEventsForBackgroundURLSession:completionHandler: and the fact that the session's delegate will eventually get the callbacks and can complete its task.
So I'm wondering how to use it with AFNetworking (if possible) to continue downloading in background.
The problem is, AFNetworking conveniently uses block based API to do all the requests, but if the app terminated or crashes those block are also gone. So how can I complete the task?
Or maybe I'm missing something here...
Let me explain what I mean:
For example my app is a photo messaging app, lets say that I have a PhotoMessage object that represent one message and this object has properties like
state - describe the state of the photo download.
resourcePath - the path to the final downloaded photo file.
So when I get a new message from the server, I create a new PhotoMessage object, and start downloading its photo resource.
PhotoMessage *newPhotoMsg = [[PhotoMessage alloc] initWithInfoFromServer:info];
newPhotoMsg.state = kStateDownloading;
self.photoDownloadTask = [[BGSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *filePath = // some file url
return filePath;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (!error) {
// update the PhotoMessage Object
newPhotoMsg.state = kStateDownloadFinished;
newPhotoMsg.resourcePath = filePath;
}
}];
[self.photoDownloadTask resume];
As you can see, I use the completion block to update that PhotoMessage object according to the response I get.
How can I accomplish that with a background transfer? This completion block won't be called and as a result, I can't update the newPhotoMsg.
A couple of thoughts:
You have to make sure you do the necessary coding outlined in the Handling iOS Background Activity section of the URL Loading System Programming Guide says:
If you are using NSURLSession in iOS, your app is automatically relaunched when a download completes. Your app’s application:handleEventsForBackgroundURLSession:completionHandler: app delegate method is responsible for recreating the appropriate session, storing a completion handler, and calling that handler when the session calls your session delegate’s URLSessionDidFinishEventsForBackgroundURLSession: method.
That guide shows some examples of what you can do. Frankly, I think the code samples discussed in the latter part of the WWDC 2013 video What’s New in Foundation Networking are even more clear.
The basic implementation of AFURLSessionManager will work in conjunction with background sessions if the app is merely suspended (you'll see your blocks called when the network tasks are done, assuming you've done the above). But as you guessed, any task-specific block parameters that are passed to the AFURLSessionManager method where you create the NSURLSessionTask for uploads and downloads are lost "if the app terminated or crashes."
For background uploads, this is an annoyance (as your task-level informational progress and completion blocks you specified when creating the task will not get called). But if you employ the session-level renditions (e.g. setTaskDidCompleteBlock and setTaskDidSendBodyDataBlock), that will get called properly (assuming you always set these blocks when you re-instantiate the session manager).
As it turns out, this issue of losing the blocks is actually more problematic for background downloads, but the solution there is very similar (do not use task-based block parameters, but rather use session-based blocks, such as setDownloadTaskDidFinishDownloadingBlock).
An alternative, you could stick with default (non-background) NSURLSession, but make sure your app requests a little time to finish the upload if the user leaves the app while the task is in progress. For example, before you create your NSURLSessionTask, you can create a UIBackgroundTaskIdentifier:
UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// handle timeout gracefully if you can
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}];
But make sure that the completion block of the network task correctly informs iOS that it is complete:
if (taskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}
This is not as powerful as a background NSURLSession (e.g., you have a limited amount of time available), but in some cases this can be useful.
Update:
I thought I'd add a practical example of how to do background downloads using AFNetworking.
First define your background manager.
//
// BackgroundSessionManager.h
//
// Created by Robert Ryan on 10/11/14.
// Copyright (c) 2014 Robert Ryan. All rights reserved.
//
#import "AFHTTPSessionManager.h"
#interface BackgroundSessionManager : AFHTTPSessionManager
+ (instancetype)sharedManager;
#property (nonatomic, copy) void (^savedCompletionHandler)(void);
#end
and
//
// BackgroundSessionManager.m
//
// Created by Robert Ryan on 10/11/14.
// Copyright (c) 2014 Robert Ryan. All rights reserved.
//
#import "BackgroundSessionManager.h"
static NSString * const kBackgroundSessionIdentifier = #"com.domain.backgroundsession";
#implementation BackgroundSessionManager
+ (instancetype)sharedManager {
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (instancetype)init {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kBackgroundSessionIdentifier];
self = [super initWithSessionConfiguration:configuration];
if (self) {
[self configureDownloadFinished]; // when download done, save file
[self configureBackgroundSessionFinished]; // when entire background session done, call completion handler
[self configureAuthentication]; // my server uses authentication, so let's handle that; if you don't use authentication challenges, you can remove this
}
return self;
}
- (void)configureDownloadFinished {
// just save the downloaded file to documents folder using filename from URL
[self setDownloadTaskDidFinishDownloadingBlock:^NSURL *(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) {
if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)downloadTask.response statusCode];
if (statusCode != 200) {
// handle error here, e.g.
NSLog(#"%# failed (statusCode = %ld)", [downloadTask.originalRequest.URL lastPathComponent], statusCode);
return nil;
}
}
NSString *filename = [downloadTask.originalRequest.URL lastPathComponent];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingPathComponent:filename];
return [NSURL fileURLWithPath:path];
}];
[self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
if (error) {
// handle error here, e.g.,
NSLog(#"%#: %#", [task.originalRequest.URL lastPathComponent], error);
}
}];
}
- (void)configureBackgroundSessionFinished {
typeof(self) __weak weakSelf = self;
[self setDidFinishEventsForBackgroundURLSessionBlock:^(NSURLSession *session) {
if (weakSelf.savedCompletionHandler) {
weakSelf.savedCompletionHandler();
weakSelf.savedCompletionHandler = nil;
}
}];
}
- (void)configureAuthentication {
NSURLCredential *myCredential = [NSURLCredential credentialWithUser:#"userid" password:#"password" persistence:NSURLCredentialPersistenceForSession];
[self setTaskDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *credential) {
if (challenge.previousFailureCount == 0) {
*credential = myCredential;
return NSURLSessionAuthChallengeUseCredential;
} else {
return NSURLSessionAuthChallengePerformDefaultHandling;
}
}];
}
#end
Make sure app delegate saves completion handler (instantiating the background session as necessary):
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
NSAssert([[BackgroundSessionManager sharedManager].session.configuration.identifier isEqualToString:identifier], #"Identifiers didn't match");
[BackgroundSessionManager sharedManager].savedCompletionHandler = completionHandler;
}
Then start your downloads:
for (NSString *filename in filenames) {
NSURL *url = [baseURL URLByAppendingPathComponent:filename];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[[BackgroundSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:nil completionHandler:nil] resume];
}
Note, I don't supply any of those task related blocks, because those aren't reliable with background sessions. (Background downloads proceed even after the app is terminated and these blocks have long disappeared.) One must rely upon the session-level, easily recreated setDownloadTaskDidFinishDownloadingBlock only.
Clearly this is a simple example (only one background session object; just saving files to the docs folder using last component of URL as the filename; etc.), but hopefully it illustrates the pattern.
It shouldn't make any difference whether or not the callbacks are blocks or not. When you instantiate an AFURLSessionManager, make sure to instantiate it with NSURLSessionConfiguration backgroundSessionConfiguration:. Also, make sure to call the manager's setDidFinishEventsForBackgroundURLSessionBlock with your callback block - this is where you should write the code typically defined in NSURLSessionDelegate's method:
URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session. This code should invoke your app delegate's background download completion handler.
One word of advice regarding background download tasks - even when running in the foreground, their timeouts are ignored, meaning you could get "stuck" on a download that's not responding. This is not documented anywhere and drove me crazy for some time. The first suspect was AFNetworking but even after calling NSURLSession directly, the behaviour remained the same.
Good luck!
AFURLSessionManager
AFURLSessionManager creates and manages an NSURLSession object based on a specified NSURLSessionConfiguration object, which conforms to <NSURLSessionTaskDelegate>, <NSURLSessionDataDelegate>, <NSURLSessionDownloadDelegate>, and <NSURLSessionDelegate>.
link to documentation here documentation

Prevent UIDocument openWithCompletionHandler being called when already opening

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.

Resources