There is a problem with a poorly chosen, non-composable abstractions in Cocoa that arise when one tries to combine a Quick Look preview panel and a security-scoped URLs.
I have a concrete example:
Imagine we are trying to show a preview for some objects from the MediaLibrary (MediaLibrary.framework allows applications to browse the iPhoto, Aperture... and Photos libraries via convenient API).
The most simple and straightforward way to do so is to adapt the 'MLMediaObject' class (that represent a particular photo or video item) to implement the 'QLPreviewItem' protocol (which can be passed to the QLPreviewPanel):
MLMediaObject+PreviewItem.h
#import <MediaLibrary/MLMediaObject.h>
#import <Quartz/Quartz.h>
@interface MLMediaObject (PreviewItem) <QLPreviewItem>
@end
MLMediaObject+PreviewItem.m
#import "MLMediaObject+PreviewItem.h"
@implementation MLMediaObject (PreviewItem)
- (NSURL*) previewItemURL
{
return self.URL;
}
- (NSString*) previewItemTitle
{
return self.name;
}
@end
Simple. Now image the following QLPreviewPanel data source implementation:
AlbumViewController.m
- (NSInteger) numberOfPreviewItemsInPreviewPanel: (QLPreviewPanel*) panel
{
// 'currentAlbum' property contains the currently-represented MLMediaGroup object.
return self.currentAlbum.count;
}
- (id<QLPreviewItem>) previewPanel: (QLPreviewPanel*) panel previewItemAtIndex: (NSInteger) index
{
return self.currentAlbum[index];
}
So far so good. But if we look into the sparse and usually misleading Apple documentation, we may find out the following important details:
URL
The location of the media object. (read-only)
This property is provided as a security-scoped URL. In order to gain access to the file that this URL refers to, the caller must callstartAccessingSecurityScopedResource
before andstopAccessingSecurityScopedResource
after using the URL to access the file.
So, it is obvious that access to the resource have to be bracketed with a startAccessingSecurityScopedResource
/stopAccessingSecurityScopedResource
calls pair.
The question is where should I put these calls given the current QLPreviewPanelDataSource
protocol definition? It is up to QLPreviewPanel to access the resource, not my code, but unfortunately I hardly ever will believe that Apple updated QL to operate in a sandboxing-wise environment.
How do I handle the cases, when startAccessingSecurityScopedResource
call returns NO
, stating about failure to obtain an access?
Seems like when you try to startAccessingSecurityScopedResource
on an URL that is already being accessed you get the failure flag on return. Like, everything is OK, but you get an error flag. Seems like these start/stop... calls have to be paired precisely, and even a balanced nesting is forbidden. So, how to differentiate between the two possibilities when you get a NO
on return: a security-scoped URL that is already being accessed and a security-scoped URL that failed to 'resolve'?
It is an experimentally-proven fact, that your application can only access a finite amount of security-scoped URLs (you can take about ~1500 URLs before it will silently stop working). So, how should I properly relinquish access to the security-scoped URLs after I've passed them to the QLPreviewPanel? When it it a right time to do so? It seems to me like it is a private implementation detail of a QLPreviewPanel class and I can make no assumptions about its internal workings.