A macOS App with a File Provider Extension
• 6 minute read
To me it was not obvious on how to get started with the file provider API on macOS. I want to share how I successfully set up an Xcode project for development.
The file provider is available on iOS since some years now. It enables unmatched integration of cloud storages like iCloud Drive. This technology, which is not as simple as it might seem because it poses conceptual requirements to the operating system and file system in use, solves a lot of headaches one has with classic synchronization clients. It is a big leap forward for those like we know them since more than a decade. Actually it changes the whole experience from synchronization processes to on-demand access with offline caching.
Where to Start?
When I wanted to try out the API on macOS Big Sur in spring 2021 it was not straight forward. The official documentation was insufficient for me as somebody not familiar with this niche framework. As usual the most information I found online was in context of iOS instead of macOS, too. Further there even were differences to the iOS version which is far more popular as it renders the only option to integrate with the file system or better said Files app. You may remember that there was no file system on iOS like on desktop operating systems before its Files app shipped. Also there was no template project for the file provider extension available in Xcode. The closest I could get was the Finder Sync extension template which is different and has to be changed to fulfill the desired rule. Without the information how it was a trial-and-error workflow. Xcode 12.5 Beta 3 to the rescue! It was published shortly after and was the first release to contain a project template for a file provider extension. So at least the basics were set up.
Build, Run and Debug
The first wall I hit was how to get the extension to be loaded and ready for debugging. This culminated in a StackOverflow question answered with a critical hint. The missing piece in the puzzle was to register a domain. In scope of the file provider framework a domain appears like a storage device in Finder. In other words I would describe its appearance like any storage device like USB drives as we see them in Finder. Before I learned about those domains I expected an extension to automatically appear and offer only one storage. In reality an extension can define multiple storage volumes or “domains” to stick to the correct terminology in this topic. This can be useful, if you imagine a generic remote file client based on SSH which could represent distinct accounts on different servers with a dedicated domain for each.
This is important to know about extensions in the context of debugging: their lifecycle is not controlled by the enclosing app. The operating system decides when they are loaded and invoked. This restriction is obliged in best interest of the user and privacy. From the experience of my colleagues and me the system is constantly scanning the whole file system for extensions to load. Even extensions in the trash or Xcode’s build directories are taken into consideration. This can be a stumbling block because the system always loads the most recent version of an extension. Whenever you observe extension behaviour diverging from your code during debugging and you start to mistrust your Mac you can check which version of your extension is loaded with this shell command:
It will list all loaded extensions with details such as the important Path information. Example:
+ com.apple.MailShareExtension(11.0) Path = /System/Applications/Mail.app/Contents/PlugIns/MailShareExtension.appex UUID = B44157BF-828C-43BF-BC1A-393F63218E5E Timestamp = 2022-01-27 08:34:42 +0000 SDK = com.apple.share-services Parent Bundle = /System/Applications/Mail.app Display Name = Mail Short Name = Mail Parent Name = Mail
Hint: the first character in the line of the extension bundle identifier is a status information.
Whenever there is a
! it means no good and there is something wrong with the extension.
Anyway, here comes the interesting part by example: set a breakpoint in Xcode in the initializer of the
Xcode will likely display the breakpoint with dashed lines only as it cannot be resolved.
If I remember correctly, this was introduced with Xcode 13. This currently is not a problem because Xcode simply is not attached to the extension process.
In case of a file provider extension it cannot be attached by simply running its scheme in Xcode.
Xcode has to attach to its process once it is there.
In the “Debug” menu of Xcode, select “Attach to Process by PID or Name…”.
Enter the name of your file provider extension. In my case this is “SomeProvider” as I have named it in my example project I am occassionally referring to from now on.
Now build and run the host app.
When the extension is loaded by the system Xcode should attach automatically to it and it should appear in the debug navigator of Xcode.
Now Xcode is abled to resolve the breakpoint. It is not invoked yet, though. Having a look in Finder and the domain offered by the extension it soon is clear why.
It has to be enabled first. Note the information bar below Finder’s tool bar. This also is a convenient new feature offered by the file provider framework. Users do not have to dig around in the system preferences anymore. They can enable the extension of your app right in Finder where they access it.
So in my case the Xcode debugger process crashed and I could not stop by the breakpoint anymore in the first try. I closed my Finder window, killed the “SomeProvider” process in the Activity Monitor of macOS, set up Xcode to automatically connect to that process by name again once it is there and navigated to the domain in Finder. Tada! Xcode pauses at the previously created breakpoint in the extension initializer. This is how you get your foot in the door or in other words: the Xcode debugger in your file provider extension process.
At this point I originally continued on how to provide account credentials from the host app to the file provider extension. That probably is one of the first and essential goals you are interested in. Once I took another look on my example code I realized this alone will likely multiply the length of this post. It involves some pieces of code in a not too trivial constellation, too.
Once I am done with writing and publishing I will update this post with a link to the followup here.