A (Re-)Introduction to ExtensionKit

3 days ago 3

ExtensionKit was a pretty significant new feature, introduced three years ago with macOS Ventura. But, I wouldn’t be surprised if you’ve never even heard of it, as it had a strangely quiet introduction. There were no sessions or labs about it during WWDC 2022. I only discovered it because a friend stumbled across the beta documentation and sent it to me.

It ended up being one of those things that arrived at just the right time. I was working quite a lot on an open source text editor, Chime. I had been interested in adding an extension system and I ended up using ExtensionKit as the basis for what turned into ChimeKit.

But, all this happened a while ago. And ever since then, I’ve been paying close attention to ExtensionKit’s status on iOS. Well, it has finally happened. ExtensionKit looks like it is now supported on iOS 26. And from what I can tell so far, thanks to some great work from Khaos, it appears to support all the same functionality as it does on macOS.

After completing the initial work on Chime’s extensions system, I wrote about my experiences with ExtensionKit. It turned out to be a big topic so I broke it up into a series. And now that this system is available for iOS, I have a weird feeling there’s going to be some renewed interest. However, after re-reading them, I don’t think the original posts aged that well. So, I decided to revise them and repost.

This first one is really just focusing on getting familiar with what ExtensionKit is all about. I’ll be updating the rest soon!

Capabilities

At a high-level, ExtensionKit allows for inter-application operation. This means that an app from developer A can build an extension which can then be loaded into an app build by developer B. Seems like a big deal.

It appears that ExtensionKit, and the lower-level ExtensionFoundation have been around for quite a while. All of the existing iOS and macOS extension features are based on it. But, it also looks like Apple has been using this system internally as well. This bodes well for any framework, and ExtensionKit has indeed proven to be pretty solid.

You can define extension points in your app, either with or without a UI component. All communication between extension and host goes over XPC, and there’s a bit of infrastructure provided by Apple for discovering available extensions and establishing a connection.

One of the most exciting things ExtensionKit can do is remote views. This is a view that is constructed with SwiftUI and managed within the extension, but displayed within the hosting application. As far as I can tell, this arrangement is totally transparent and supports virtually everything that SwiftUI can do, even animation. Perhaps the only real downside is window/view resizing can sometimes have a little lag.

Communication

Communication between extension and host is all done using XPC. There’s one global XPC connection per extension, but there’s also an optional connection per UI scene. At first, this was surprising to me. But, once I started actually experimenting with UIs, I realized this arrangement makes a lot of sense. While there will be only one extension instance, an extension can be asked to render multiple instances of its views. Of course, your design may not support this. But, if you do, this is a good means of coordination.

Extension hosts (apps that support loading extensions) will almost certainly want to provide some kind of framework that defines a higher-level API for extension-host interaction. This is an area that can demand some careful work. First, you want to provide a reasonable API for extension developers. But, you also need to consider forwards- and backwards-compatibility. There’s nothing provided by ExtensionKit to keep these APIs in sync across the versions of extensions and app that your users have installed. There is also no way, as far as I can tell, to get any metadata about an extension other than its host app bundle and extension point id.

New in the 26 releases is support for XPCSession. I haven’t yet had a chance to play around with this. But, when XCPSession was first introduced I did attempt to make a backwards-compatible wrapper around it called XPCConnectionSession. Perhaps I should revisit this now!

In the meantime, however, you might like this package, which provides concurrency support for NSXPCConnection.

Sandboxing

While not really a new thing for iOS, on macOS ExtensionKit will refuse to load any extensions that do not have the sandbox entitlement set. How much of a problem this is really depends on what your extension developers need to do. Fortunately, if it is a problem, you have some options.

It is possible to pass URL bookmark data across the XPC connection to share access. I’ve found it pretty confusing to understand how to use the security features of bookmarks, but in this case, it’s proven to be easy.

// create the data on the side that has access let data = try url.bookmarkData() // a side-effect of resolving it on the other end will grant the process access var stale: Bool = false let _ = try URL( resolvingBookmarkData: data, options: [.withoutUI], relativeTo: nil, bookmarkDataIsStale: &stale )

If sharing file access isn’t sufficient, and it definitely was not for Chime, you’ll need to turn to other methods. Having a direct IPC mechanism via XPC is pretty darn powerful and that could be a useful tool for stuff like this.

Distribution

An interesting quirk of ExtensionKit is that extensions must be bundled and delivered within a .app. They cannot be distributed as standalone artifacts. I think this makes a lot of sense when you are attempting to expose functionality of an existing application in the form of an extension. But, there could be reasons why you might want to make only the extension itself. This even makes sense for Apple’s own supported use-cases, like Safari and Xcode editor extensions.

Now, you totally can just make a little wrapper app that does nothing but contain your extension. You can even get these minimal, placeholder apps onto the App Store. While I think this is a pain, I don’t imagine it will be changing any time soon.

Interestingly, it is also possible to bundle extensions directly within the hosting application. At first, this might seem like a strange thing to do. But it can make sense, for example to improve security. This seems like it is one of the primary intended uses for ExtensionKit on iOS.

Approval

Regardless of how you get an extension onto a user’s device, there’s one more required step before they can be used. Extensions have to be user-approved via EXAppExtensionBrowserViewController. This controller presents a UI that displays all available extensions, organized by containing application. You’ll have to find a place to put this view within your hosting app. And, perhaps adding to the integration complexity, users can approve and revoke that approval at any time.

Requiring user approval is reasonable. And, I’m really happy to say that extensions bundled within the hosting app itself are auto-approved.

On macOS, extensions approval status is visible within System Settings, under “Login Items & Extensions”. The UI for this isn’t as detailed as I’d like and I have found it to be particularly painful during the debugging cycle in the past. Maybe there are improvements in 26 though…

iOS Support?

(I’m leaving this section untouched from my original 3 year old post because VINDICATION)

Right now, hosting ExtensionKit extensions can only be done from macOS. But, as far as I can tell, all of the extension-side APIs are available to iOS, tvOS and watchOS as well. I think this, combined with the extremely conspicuous lack of WWDC sessions means ExtensionKit was supposed to ship with iOS 16, but was pulled at the last minute.

I’ll also submit as evidence EXAppExtensionBrowserViewController.h as found in Xcode 14.0 beta 6:

#if !TARGET_OS_WATCH #if TARGET_OS_OSX #import <AppKit/AppKit.h> #else #import <UIKit/UIKit.h> #endif // TARGET_OS_OSX NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(macos(13.0)) API_UNAVAILABLE(ios, watchos, tvos) EXTENSIONKIT_EXPORT #if TARGET_OS_OSX @interface EXAppExtensionBrowserViewController : NSViewController @end #else @interface EXAppExtensionBrowserViewController : UIViewController @end #endif // TARGET_OS_OSX NS_ASSUME_NONNULL_END #endif // !TARGET_OS_WATCH

Today, I’m not sure you can do too much with ExtensionKit on non-macOS platforms. But, when you consider just how many entirely new types of apps could be built with it, just the idea is exciting.

Alternatives

Lots of apps offer some kind of plugin system. I would guess that nearly all use JavaScript to do it. There are many reasons why this makes sense. First, JavaScript is a very well-known and well-used language, with an enormous number of available libraries. Second, there are many (node, deno, bun) JavaScript runtimes available, including Apple’s own JavaScriptCore.

But, another big advantage is control over installation and execution. Any language that requires static compilation will have to deal with code-signing. ExtensionKit makes things even more complex with sandboxing, app-bundled delivery, and approval. Things are just much simpler, and potentially much more end-user-friendly with something like JavaScript.

Now, of course ExtensionKit does have some pretty compelling features. It’s all Swift, providing easy access to all of Apple’s first-party APIs. Its remote view capabilities are really amazing. And, it offers a way for 3rd-party apps to integrate with each other in a way that is straightforward and well-supported. I’m sure all of these things are possible with another approach (on macOS anyways), but ExtensionKit makes them all easy.

Uses

My use for this was kind of niche and I bet it will prove to be atypical. But, I can see this becoming quite a thing. Many applications are very focused on solving a specific problem. Take a photo-editing app. No doubt it would come with a built-in camera control. But, there are dedicated camera applications out there that are far more capable.

With ExtensionKit, both apps could get more valuable. The editing app suddenly now gets access to super-high-quality camera apps that are familiar to their users. And those camera apps now have a whole new means of appealing to users. Direct integration like this would be awesome for both.

I think this could open the door to a lot of collaboration and commercial opportunities. My gut says independent developers will be particularly excited abou this, since they are already in a good position to collaborate and put in the necessary technical work.

But make no mistake, that technical work is non-trivial. You really do have to design, build, and maintain an API to make this all possible. And only once that’s done can extensions actually be built. This stuff is not easy.

Because of the high bar here, I’m excited at the possibility of standardized interfaces across extensions. This could reduce the barrier to get started and enable more compatible extensions, more quickly. Such a thing could be quite hard to actually pull off, but I’m optimistic. If you have ideas let me know!

Next Up

I certainly wasn’t expecting ExtensionKit when it was first introduced, but I’m really happy it showed up. And I’m delighted to see it has finally arrived for iOS. It made working on ChimeKit really fun, and I’m excited about what it makes possible.

ExtensionKit, and Chime’s use of it, turned out to be a pretty big topic. This is just an introduction, but hopefully it’s gotten you interested in learning more. Next up, I’ll be getting into details on the communication infrastructure used to build an ExtensionKit-based framework.

But, if you cannot wait to dig in more, check out Extendable, an open source package for working with ExtensionKit. It needs some attention, but I wanted to at least let people know it existed.


Sponsorship helps me do my writing. I also do consulting, training, and run workshops specifically for Swift concurrency. Get in touch if you think I could help.

Read Entire Article