Is it possible to write Quick Look plugins in Swift?

I am trying to write a Quick Look plugin with Swift. I am able to build successfully by change the .c to .m. However, I couldn't find a way to let the swift dylibs loaded. I pointed the runpath to @load_path/../Frameworks but it kept on "being ignored in restricted program because it is a relative path". Does that mean I am not able to build a quick look plugin in Swift?

Accepted Reply

Is it possible to write Quick Look plugins in Swift?

No. You could probably make this work but it’s definitely not safe.

The fundamental problem here is the Swift runtime. Quick Look plug-ins are hosted in a system process and it’s possible that that system process might load multiple plug-ins. If there are two plug-ins written in Swift, they might have different versions of the Swift runtime and that will not end well.

There’s two ways forward here:

  • Eventually Swift will reach ABI stability, at which point there will be a single system runtime and all plug-ins can assume that. This is how things currently work with Objective-C.

  • In recent years we’ve introduced a new mechanism for plug-ins, based on app extensions. App extension-based plug-ins run in their own process, so there’s no possibility of a conflict like this. It’s not hard to imagine Quick Look moving over to this new architecture.

The Swift runtime issue is well understood and there’s no need for you to take action there. OTOH, if you’d like to file an enhancement request for Quick Look to support the app extension model, I think that’d be valuable.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Is it possible to write Quick Look plugins in Swift?

No. You could probably make this work but it’s definitely not safe.

The fundamental problem here is the Swift runtime. Quick Look plug-ins are hosted in a system process and it’s possible that that system process might load multiple plug-ins. If there are two plug-ins written in Swift, they might have different versions of the Swift runtime and that will not end well.

There’s two ways forward here:

  • Eventually Swift will reach ABI stability, at which point there will be a single system runtime and all plug-ins can assume that. This is how things currently work with Objective-C.

  • In recent years we’ve introduced a new mechanism for plug-ins, based on app extensions. App extension-based plug-ins run in their own process, so there’s no possibility of a conflict like this. It’s not hard to imagine Quick Look moving over to this new architecture.

The Swift runtime issue is well understood and there’s no need for you to take action there. OTOH, if you’d like to file an enhancement request for Quick Look to support the app extension model, I think that’d be valuable.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks, Quinn. I just ran into this problem and filed Radar #38792518.

It seems like maybe I missed the memo about QuickLook app extension coming in 10.13? I'm looking into usin the QuickLook Preview Extension approach now.

Unfortunately the new App Extension support for QuickLook Preview Extensions does not extend to "file" previews such as you expect to see when previewing a file in the Finder. I tried applying a workaround in which a XPC task is spawned to take care of the preview with its own Swift dependencies, but I haven't been able to get that to load in my QuickLook plugin. I am not sure if it's possible.

Hey Jalkut, I'm also working on trying to get XPC to work from a quicklook context. I would love to chat with you if you have some time. Feel free to reach out to me at alexandermomchilov at gmail dot com

I'm nearly certain that it's impossible to use an XPC service in a `quicklookd` context, at least on 10.14 Mojave. After spending days on this, I figured out why.


If you try to call an XPC Service, your invalidation handler will always be called, with practically no debug information to troubleshoot with. For one, if you look at the value of `NSBundle.mainBundle.bundlePath` you get `/System/Library/Frameworks/QuickLook.framework/Versions/A/Resources/quicklookd.app/Contents/XPCServices/QuickLookSatellite.xpc`.


As a troubleshooting stab in the dark, I tried copying my XPCService into `/System/Library/Frameworks/QuickLook.framework/Versions/A/Resources/quicklookd.app/Contents/XPCServices/QuickLookSatellite.xpc/Contents/XPCServices`, my invalidation handler stopped being callled, and my interurpting handler started to be called. That means that the XPC connection was succeeding, at least initially, before being interruptted. That means the prior XPC failures were caused by an inability for the qlgenerator to find `Foo.qlgenerator/Contents/XPCServices/Foo.xpc`. This is because the path being searched was actually `.../QuickLookSatellite.xpc/Contents/XPCServices`, and *not* `Foo.qlgenerator/Contents/XPCServices`.


Even if I figure out the cause of the crash that causes the connection interupption, this is an infeasible solution approach. For one, I don't think copying files into `/System/Library/Frameworks/QuickLook.framework/Versions/A/Resources/quicklookd.app/Contents/XPCServices/` is possible while SIP is enabled, and at the minimum, it would require root permission. Any hope of App Store distribution eligibility is probably gone.
This is a huge disappointment. To support quick look, I would need to rewrite all my Swift file-parsing and rendering code in Objective C. I'm not going to do that.

as of 2020 even in Xcode 11.3.1 it is possible..

Code Block swift
class PreviewViewController: NSViewController, QLPreviewingController {
override var nibName: NSNib.Name? {
        return NSNib.Name("PreviewViewController")
    }
    override func loadView() {
        super.loadView()
/* ... */
    }
func preparePreviewOfFile(at url: URL, completionHandler handler: @escaping (Error?) -> Void) {
        /* Add the supported content types to the QLSupportedContentTypes array in the Info.plist of the extension.
        // Perform any setup necessary in order to prepare the view.
        // Call the completion handler so Quick Look knows that the preview is fully loaded.
        // Quick Look will display a loading spinner while the completion handler is not called. */
        handler(nil)
    }
}

and you don't need to load any swift dylibs..
And as far i understand Eskimo's explaining.. that circumstance is also valid for objective-c. You could end up with different librarys running at the same time.. but even tho, there is no problem doing so. Thats what all apps do more or less. Just go the way Xcode offers you. Build & Run, be happy