Internal sandbox error in a document-based app - objective-c

I have a sandboxed document-based (NSDocument) app. When saving files, I get weird sandboxing errors:
[scoped] handle 0: sandbox_extension_release error [22: Invalid argument]
[scoped] <0x600001278f00 file:///Users/username/Testi%203.exampletext>: internal sandbox error for <StopAccessing>
I'm also fetching file attributes for recent files using recentDocumentURLs in document controller, and display them in NSOutlineView. When I've accessed enough files, I start getting a new sandbox error, sandbox_extension_consume returned 12, and after that sandbox sometimes blocks the app from accessing any files.
This even happens when creating a minimal document-based app from the Xcode template. Things get saved correctly, and I can access the files I want, but errors start stacking up.
Here is a sample project which reproduces the behavior:
https://www.dropbox.com/s/9v0v65jbqkjb7ra/Sandboxed%20Doc%20App.zip?dl=1
Create a new document (window only shows the basic template, to keep it minimal) and save it. Sandbox error is immediately logged.
Document subclass in the sample is as bare bones as possible:
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
return [_string dataUsingEncoding:NSUTF8StringEncoding];
}
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
_string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return YES;
}
AFAIK, NSDocumentController should support sandboxing without any subclassing. Also, trying to manually stop accessing scoped bookmark of the document URL (after saving or on document close) produces an error.
Similar sandbox bug is referred to on Twitter, but I'm hardly accessing couple of files, and not over 4000.
What am I doing wrong, or is this a macOS Big Sur bug? And if not, how can it be present even in the document-based app template?

The issue here was that my app was requesting the list of recent files from NSDocumentController, and checking the edited dated by once again requesting that same list.
There seems to be a limit for how many files the app can rapidly access outside its sandbox, even if it has the permission to do so, and sandbox quickly runs out of extensions.
To fix the issue, check your code for anything that runs through a list of files and accesses them.

Related

iOS 9 Universal Links - no parameters recieved

Using the new iOS 9 feature - Universal links, from my understanding, is supposed top open my app whenever a specific domain is opened in browser (or other apps?). I have gone through the documentation and through this guide.
However, when the app opens I do not receive the parameter that is meant to help me open the correct page for the user to view....
I would share the code I'm using, but it's quite a big infrastructure and not really a couple of lines of code (server side JSON, plist rows and some IDs on the developer portal).
Anyone encountered it and could give me a hand here, please?
The Branch guide you linked to (full disclosure: I work with the Branch team) unfortunately doesn't cover a rather important step: what to do after your app opens. Which is exactly the issue you're encountering :). But the good news is you've already done the hard part with all the server and entitlement config.
What you need to complete the loop is a continueUserActivity handler in your AppDelegate.m file. This will pass you a webpageURL property containing the actual URL of the Universal Link that opened your app, which you can then parse and use for routing. It'll look something like this:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
NSString *myUrl = [userActivity.webpageURL absoluteString];
// parse URL string or access query params
}
return YES;
}
Also, when testing keep in mind that Universal Links unfortunately don't work everywhere yet:
P.S., gotta ask...since you found the Branch blog already, had you considered using the service to handle the link routing for you? It can definitely help simplify things!

Access contents of Watchkit extension's host's mainBundle

I'm writing a WatchKit extension and I'd like to read a file out of the host application's [NSBundle mainBundle]. I've tried [NSBundle bundleWithIdentifier:] but that just returns nil.
I have several potential workarounds, but nothing that would be as simple as "just read what you need from the host's mainBundle".
Is there a way of doing this?
The host app and your WatchKit extension can share files in only one of two ways, as far as I know:
Shared app group
Including a file in both targets
They run in separate processes and aren't accessible to each other outside of approved methods.
I ran into a similar problem like yours. The main host app has a particular pList that I needed to read, and I couldn't read from watch extension because they are isolated.
So in the watch I invoked the openParentApplication method
and in the main application my handler was something along the lines of
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
NSString *request = [userInfo objectForKey:#"request"];
if ([request isEqualToString:ReadFile])
{
//read the file. and then i like to put it into a NSDictionary
NSDictionary *responseDictionary = //whatever
reply(responseDictionary);
}
else{ reply(nil); }
}
And then the contents were returned to me in the callback closure on the watch of the openParentApplication. Seems to work. Though your situation could be different in which case this method might not be viable.
From the Apple WatchKit programming guide:
To share preferences data between apps, create an NSUserDefaults object using the identifier of the shared group. The initWithSuiteName: method of NSUserDefaults creates an object that allows access to the shared user defaults data. Both processes can access this data and write changes to it.
Your main app can write a NSDictionary/NSArray to the shared prefs, and then the watch kit can pull it out, without starting the main app - however, the main app will have to be run at least once to update the shared prefs.

Always appending file extensions in NSDocument

What would be the best way to always ensure the file saved to disk (or iCloud) contains the default file extension for our document format in an NSDocument based Cocoa app?
Background:
Users can load legacy files into the app that still use Type-Creator-Codes.
With auto-saving enabled in our app we need to make sure the file always has the file extension added as soon as it's written back to the disk (following any kind of changes) with our Cocoa app - or the app won't be able to open it (with now neither the type-creator-code nor the file extension).
If I got it right I'd overwrite NSDocumentController's open method
- (void)openDocumentWithContentsOfURL:(NSURL *)url
display:(BOOL)displayDocument
completionHandler:(void (^)(NSDocument *document,
BOOL documentWasAlreadyOpen,
NSError *error))completionHandler {
if(!url.pathExtension isEqualToString:#"XYZ")
url = url URLByAppendingPathExtension:#"XYZ"];
[super openDocumentWithContentsOfURL:url
display:displayDocument
completionHandler:completionHandler];
}
NOTE: written inline

Couple service related "bugs"

I've written an application that can be invoked as a service (by right-clicking a file in Finder and selecting to open it with my application), but there are a couple of unwanted side-effects when doing this.
Example of service target method:
- (void)doSomething:(NSPasteboard *)pboard userData:(NSString *)userData error:(NSString **)error {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"Selected file(s): %#", files);
[self.anotherWindow makeKeyAndOrderFront:self];
}
1) When the application is launched this way (even if already open in debug mode), I seem unable to access other windows/controls from the doSomething function. The above attempt to show "anotherWindow", for example, produces no errors, but doesn't "do" anything. According to the stack trace, when inspected from -doSomething, all gui components have values 0x0000000000000000 - yet the application is displayed and fully functional. It's only from -doSomething that I cannot reach them. "self" also has a different value when inspected from -doSomething versus -applicationDidFinishLaunching. I'm not sure how or why -doSomething is acquiring a different self/AppDelegate with uninitialized components. Seemingly fixed by [NSApp setServicesProvider:self];
2) I am not clear on how the system decides which copy of the application to launch when the service is invoked, but it usually doesn't pick the one I want. I have a copy in /Debug, a copy in /Release, a copy on my desktop... and if I delete one, it opens the file with another one instead (some sort of fallback-chain?). How do I configure the service (in code or thru .plist) to open a specific version/location of this app? But this is a dev machine. If I release a distributable which installs to /Applications, do I ever really need to worry about this?
1) Double-check your XIB to makes sure that you've got everything hooked up correctly and then try launching the app with a breakpoint set at the NSLog above and verify that self.anotherWindow points at what you want. If, for some reason, the breakpoint isn't firing, trying adding an:
NSLog( #"Window: %#", self.anotherWindow);
To make sure everything is initialized and hooked up
2) The system uses Launch Services to determine which version of the application to launch. Often it is the version most recently added to the system (which will cause the Launch Services database to be modified), but it is possible, depending on how your system is configured, that it won't be the version you expect.
You can manually inquire and modify the launch services database using:
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister
(yeah, really long path). If you use the -dump option, that'll give you all of the data in the system (pipe into a file and search through it to get a better idea what's going on). If you search of the bundle id, you'll see all of the entries for the app. Generally, most recent wins, but you can force a reload (instructions below).
If you just want to force a reload based on a a particular binary, use the -f flag and the path to the application:
..../lsregister -f /Applications/Foo.app
You can also use -u to explicitly unregister something.
Hopefully this will give you an idea what's going on here.

Are iCloud images being cached in iOS device after being retrieved by application?

I have a simple UITableView, where each cell has an thumbnail picture that the user may have taken with its iOS device camera.
If iCloud is enabled the image is saved in it. However I was wondering if some sort of caching happens when loading the image, because I have notice a slowness on first loading then, even if the piece of code is called again when cell display on screen, the image show quite fast.
This is the relevant code fragment, I have omitted the logic for building the cell, I think it is not relevant, because the question is about other aspects:
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// logic for retrieve data and build cell
NSURL *ubiquityUrl = [fm URLForUbiquityContainerIdentifier:nil];
NSURL *docURL = [ubiquityUrl
URLByAppendingPathComponent:[NSString stringWithFormat:#"P_%#_%#.jpg",imgId,#"thumbnail"]
isDirectory:NO];
// this a custom object extending UIDocument
IP2DataDocument *dataDocument = [[IP2DataDocument alloc] initWithFileURL:docURL];
[dataDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"iCloud document opened");
// logic for filling table cell picture
} else {
NSLog(#"failed opening document from iCloud");
}
}];
// returning cell
}
I can see "iCloud document opened" in Xcode, each time the cell display.
If some sort of caching occurs, can you point out where and how ?
The Document-based App Programming Guide for iOS says:
When you run a metadata query to learn about an application’s iCloud documents, the query results are placeholder items (NSMetadataItem objects) for document files. The items contain metadata about the file, such as its URL and its modification date. The document file is not in the iCloud container directory.
The actual data for a document is not downloaded until one of the following happens:
Your application attempts to open or access the file, such as by calling openWithCompletionHandler:.
Your application calls the NSFileManager method startDownloadingUbiquitousItemAtURL:error: to download the data explicitly.
To sum it up: The first time you open your document, it may not have been downloaded from iCloud yet, which is why it'll take longer. Afterwards, there is a local copy of the file that is obviously faster to read.
It is your docURL or specially
NSURL *ubiquityUrl = [fm URLForUbiquityContainerIdentifier:nil];
You don't actual access directly to iCloud. You access an equivalent local location (ubiquityUrl) that is iCloud enabled. If the file that you try to get has not been downloaded locally yet, then the local iCloud daemon will download it at the time you are accessing. Once this occurs, it is stored in your device locally. So the next time, you try to access it again using the docURL, it is much quicker because of this. Also the iCloud daemon is responsible for syncing version your document. So you don't need to worry about that if someone else has updated a newer version of the same document from another device.