Can I Write a Spotlight Importer in Swift? - macos

I need to write a Spotlight Importer for an application that I've written in Swift, and am referring to the official Apple guide for Writing a Spotlight Importer.
It seems straightforward enough, however creating a Spotlight Importer project creates a default setup for an Objective-C implementation. Now, working with Objective-C isn't a huge problem (I've used it plenty of times in the past) but everything I've written for my application is in Swift, so I'd really I'd like to write the importer in Swift too to avoid switching between languages, and also so I can share some of the code that I've already done for reading/writing files.
Firstly, is it possible to write a Spotlight Importer using Swift instead of Objective-C? And if it is, where should I start (e.g- if I take the Objective-C starting point, what would I do to switch over to Swift instead)?

It took me a bit of time to get this to work.
Instead of adding Swift code to the mdimporter, I import an embedded framework already setup for my app.
I removed all the example code except main.c and GetMetadataForFile.m.
In the latter I import my framework where all the functionality now resides as Swift code.
The built mdimporter is added to the app.
In the File Inspector set Location to Relative to Build Products.
The app then adds the mdimporter with a Copy Files Build Phase.
Destination: Wrapper
Subpath: Contents/Library/Spotlight
The following needs to be added to the Run Search Paths build setting, as we are linking to the app's embedded frameworks.
#loader_path/../../../../../Frameworks
If you get compiler error that the framework module can't be found when building the app, depending on how your workspace is set up, you might need to modify your app's Scheme.
Turn off Parallelize Build
Add the Build targets in this sequence:
Frameworks project(s)
mdimporter project
App project
The additional benefit of having all the logic in a framework, is that it can be prototyped and verified in a Playground. A million times easier than debugging an mdimporter plugin.

Yes, it is possible to write a Spotlight Importer entirely* in Swift!
*except for a few lines of code in main.m
I've just published one here: https://github.com/foxglove/MCAPSpotlightImporter
Here's a detailed blog post about the implementation process:
https://foxglove.dev/blog/implementing-a-macos-search-plugin-for-robotics-data
The difficult part of this is implementing a plugin that's compatible with the CFPlugIn architecture. (The MDImporter-specific logic is relatively minimal.) The CFPlugIn API is based on Microsoft's COM and Apple's docs are almost 20 years old.
The plugin is expected to be a block of memory conforming to a certain memory layout — specifically, the first value in the block must be a pointer to a virtual function table (vtable) for the requested interface (in the case of a MDImporter, this is either MDImporterInterfaceStruct or MDImporterURLInterfaceStruct) or the base IUnknown interface. This layout is documented here.
I wanted to organize the Swift code into a class, but you can't control the memory layout of a Swift class instance. So I created a "wrapper" block of memory which holds the vtable and an unsafe pointer to the class instance. The class has a static func allocate() which uses UnsafeMutablePointer to allocate the wrapper block, create and store the class instance in it, and also initialize the vtable.
The vtable implements the standard COM base interface (IUnknown) functions (QueryInterface, AddRef, and Release) by grabbing the class instance out of the wrapper and calling the queryInterface(), addRef(), and release() methods on the instance. It also implements the Spotlight-specific ImporterImportURLData function (or ImporterImportData). Unfortunately, in my testing, it seemed like Spotlight did not pass the correct pointer to the wrapper struct as the first argument to ImporterImportURLData, so it was impossible to call a method on the class instance, so the function that actually imports attributes for a file had to be a global function. For this reason I wasn't able to make the plug-in implementation a more generic class that could be used with any interface — it has to be tied to a specific global importer function.
I'd encourage you to view the full source on GitHub, but in the interest of not being a link-only answer, here's the core functionality:
final class ImporterPlugin {
typealias VTable = MDImporterURLInterfaceStruct
typealias Wrapper = (vtablePtr: UnsafeMutablePointer<VTable>, instance: UnsafeMutableRawPointer)
let wrapperPtr: UnsafeMutablePointer<Wrapper>
var refCount = 1
let factoryUUID: CFUUID
private init(wrapperPtr: UnsafeMutablePointer<Wrapper>, factoryUUID: CFUUID) {
self.wrapperPtr = wrapperPtr
self.factoryUUID = factoryUUID
CFPlugInAddInstanceForFactory(factoryUUID)
}
deinit {
let uuid = UUID(factoryUUID)
CFPlugInRemoveInstanceForFactory(factoryUUID)
}
static func fromWrapper(_ plugin: UnsafeMutableRawPointer?) -> Self? {
if let wrapper = plugin?.assumingMemoryBound(to: Wrapper.self) {
return Unmanaged<Self>.fromOpaque(wrapper.pointee.instance).takeUnretainedValue()
}
return nil
}
func queryInterface(uuid: UUID) -> UnsafeMutablePointer<Wrapper>? {
if uuid == kMDImporterURLInterfaceID || uuid == IUnknownUUID {
addRef()
return wrapperPtr
}
return nil
}
func addRef() {
precondition(refCount > 0)
refCount += 1
}
func release() {
precondition(refCount > 0)
refCount -= 1
if refCount == 0 {
Unmanaged<ImporterPlugin>.fromOpaque(wrapperPtr.pointee.instance).release()
wrapperPtr.pointee.vtablePtr.deinitialize(count: 1)
wrapperPtr.pointee.vtablePtr.deallocate()
wrapperPtr.deinitialize(count: 1)
wrapperPtr.deallocate()
}
}
static func allocate(factoryUUID: CFUUID) -> Self {
let wrapperPtr = UnsafeMutablePointer<Wrapper>.allocate(capacity: 1)
let vtablePtr = UnsafeMutablePointer<VTable>.allocate(capacity: 1)
let instance = Self(wrapperPtr: wrapperPtr, factoryUUID: factoryUUID)
let unmanaged = Unmanaged.passRetained(instance)
vtablePtr.initialize(to: VTable(
_reserved: nil,
QueryInterface: { wrapper, iid, outInterface in
if let instance = ImporterPlugin.fromWrapper(wrapper) {
if let interface = instance.queryInterface(uuid: UUID(iid)) {
outInterface?.pointee = UnsafeMutableRawPointer(interface)
return S_OK
}
}
outInterface?.pointee = nil
return HRESULT(bitPattern: 0x8000_0004) // E_NOINTERFACE <https://github.com/apple/swift/issues/61851>
},
AddRef: { wrapper in
if let instance = ImporterPlugin.fromWrapper(wrapper) {
instance.addRef()
}
return 0 // optional
},
Release: { wrapper in
if let instance = ImporterPlugin.fromWrapper(wrapper) {
instance.release()
}
return 0 // optional
},
ImporterImportURLData: { _, mutableAttributes, contentTypeUTI, url in
// Note: in practice, the first argument `wrapper` has the wrong value passed to it, so we can't use it here
guard let contentTypeUTI = contentTypeUTI as String?,
let url = url as URL?,
let mutableAttributes = mutableAttributes as NSMutableDictionary?
else {
return false
}
var attributes: [AnyHashable: Any] = mutableAttributes as NSDictionary as Dictionary
// Call custom global function to import attributes
let result = importAttributes(&attributes, forFileAt: url, contentTypeUTI: contentTypeUTI)
mutableAttributes.removeAllObjects()
mutableAttributes.addEntries(from: attributes)
return DarwinBoolean(result)
}
))
wrapperPtr.initialize(to: (vtablePtr: vtablePtr, instance: unmanaged.toOpaque()))
return instance
}
}
Finally, I created an #objc class that exposes this allocate function to Obj-C, where I can call it from main.m, and return the pointer to the wrapper block from the factory function. This was necessary because I didn't want to use the unstable #_cdecl attribute to expose a Swift function directly to the plug-in loader.
#objc public final class PluginFactory: NSObject {
#objc public static func createPlugin(ofType type: CFUUID, factoryUUID: CFUUID) -> UnsafeMutableRawPointer? {
if UUID(type) == kMDImporterTypeID {
return UnsafeMutableRawPointer(ImporterPlugin.allocate(factoryUUID: factoryUUID).wrapperPtr)
}
return nil
}
}
// main.m
void *MyImporterPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID) {
return [PluginFactory createPluginOfType:typeID factoryUUID:CFUUIDCreateFromString(NULL, CFSTR("your plugin factory uuid"))];
}
See my blog post for more details.

Since Apple introduced Swift as a language to be perfectly compatible with any existing Objective-C project I would suggest you just start with whatever makes things easier for you.
If you know Swift best then nothing keeps you from using that – for whatever project you might want. If you want to follow a tutorial that was written for Objective-C and not updated for Swift yet, I think you have two choices (I'd personally recommend going for the second option for now):
Write the same logic written in Objective-C within the tutorial now in Swift from scratch (nearly everything possible in Objective-C is easily possible with Swift, too). For that you need to understand the basics of Objective-C and the corresponding syntax and features in Swift though.
Start with Objective-C to follow the tutorial and keep things easier at the beginning (no need to really understand the tutorials details). Then use the great possibility of mix and matching Swift code alongside Objective-C code to customize the code for your needs or to extend it with your own pre-existing classes.
More specifically on the second option:
If you want to write new classes just use Swift – you can perfectly use everything written in Objective-C from within Swift and vice versa. If you feel you need to change classes already written in Objective-C you have these options: Extend the class written in Objective-C with a new Swift class, re-write that specific file in Swift or just edit the Objective-C file directly.
To learn more on how to mix and match Swift code alongside Objective-C I recommend reading Apples official documentation. It's part of the free iBook "Using Swift with Cocoa and Objective-C" written by Apple engineers for developers.
Unfortunately Apple actually does seem to provide their template for a Spotlight Importer from within XCode for Objective-C only at the moment. Don't know why this is though – I can't see anything stopping them from supporting Swift. We should probably report this with Apples Bug Reporter to stress the fact that people are actually asking for this.
Hope I didn't overlook anything here, otherwise my answer will be pointless. ^^
UPDATE (request)
Here are some steps on where to begin to implement the first approach:
First create a Spotlight Importer project with the latest XCode version
– Create a new "Cocoa Touch" class named exactly the same as your pre-created main Objective-C classes (e.g. "MySpotlightImporter")
Choose Swift and "Create Bridging Header" when asked during class creation
– Re-implement the code written in the ObjC-MySpotlightImporter class within the Swift class (you might want to create a Cocoa App with Core Data support in Swift and Objective-C to get some idea of their differences)
– I'm not sure if you can rewrite the GetMetaDataFile.m in Swift, too, I couldn't figure that out in my test, so you maybe need to keep it around (for now)
– In case you receive any errors along the way that point to some missing configuration just search for the related files/classes in the projects "Build settings" and apply your changes there
I hope this helps to get you started and is specific enough. I tried to do the necessary changes myself in order to provide an example project in Swift but unfortunately I couldn't get it working in a limited time. You may want to consider providing your code publicly though (e.g. on GitHub with a link posted here) in case you decide to port it yourself so others can profit from this, too.
Good luck!

Related

How to get available memory in IOS using Xamarin

I'm porting an application I built in the past using Appcelerator. This app creates tons of dynamic content, so there is a function made to watch memory. In other words: Explanations like : use the DidReceiveMemoryWarning() to clear cache or use external tools to watch memory is not applicable. The end user must observe how big and heavy your on-the-fly content is consuming. The app made using Appcelerator uses Titanium.Platform.availableMemory and it's pretty easy to use.
I can't believe that's not possible using Xamarin. Is it really impossible ? I found a library converted from Objective-C to .Net, but it's probably very old because it can't compile.
Any help in this sense ?
You can use System.GC.GetTotalMemory(false); to get the current memory. If you need available memory, you can use NSProcessInfo.ProcessInfo.PhysicalMemory. You could then override your own behavior of DidReceiveMemoryWarning() to clean up excess resources.
EX:
public override void DidReceiveMemoryWarning ()
{
photoMap.Clear ();
View = null;
photoImageView = null;
toolbar = null;
syncIsNeeded = true;
base.DidReceiveMemoryWarning();
}

Extension of a nested type in Swift

I have a main class, also providing a namespace:
class A {
}
and a nested class added via an extension (all for the sake of using separate files):
extension A {
class B {
}
}
I want to add functionality to the nested class (B) by extending it; I've tried:
extension A.B {
}
I get "'B' is not a member type of 'A'".
(I've also tried some less reasonable things but I will omit them here to avoid embarrassment. Reading Swift docs and Googling for "swift nested class extension" have not yielded an answer either.)
Any idea if and how that could be accomplished?
UPDATE:
This code works as expected when in a single file (or in a Playground), thanks to user3441734 for trying it out!
Still does not work when the 3 parts are in separate files, perhaps a bug in current implementation of the Swift compiler. I will submit a bug report to Apple.
It seems like this problem is related to SR-631. I've encountered similar a issue, I guess the complier is trying to process the file where you extend the nested class before the one where it's defined. Therefore you have this error saying that that A has no member B.
The solution I've found is to go to your target settings, open Build Phases.
There, in Compile Sources section you should put the file where you define the nested class above files where you extend it.
Update
The fix will be shipping with Xcode 10.2
this works in my playground, as expected
class A {
}
extension A {
class B {
}
}
extension A.B {
func foo() {
print("print from extension A.B")
}
}
let ab = A.B()
ab.foo() // print from extension A.B

Swift 2.0 / Xcode 7B5 - Classes can only be found if put in a certain file

I noticed that at a given point the class User could not be found when I wanted to use it in other classes. Nothing suspicious about it. At a given point I started to experiment with what place I put the code.
If I put all these classes together in one of my older files, all compiles. If I put them all together in a newer file, the other parts of my code consuming these classes do not compile. If I rename a file (from User.swift to AUser.Swift for example) nothing changes, meaning what compiles keeps compiling and what doesn't compile still doesn't compile based on name. It really seems to be the age of the file or something like that.
It appears as if everything I add later related to this particular cluster of classes will not compile in a newer file. These files are included when building, I checked that. There is nothing strange about the code I think:
import UIKit
public class User: AddressBookContact {
var home: Home?
var friends = [Friend]()
}
public class Friend: AddressBookContact {
}
class Session: NSObject {
private static let instance = Session()
override private init() {
super.init()
}
class func sharedInstance() -> Session {
return instance
}
static var loggedInUser: User?
}
Look in the File Inspector Utility pane (the right-hand pane). The file in question likely is not a member of the target.

Golang events: EventEmitter / dispatcher for plugin architecture

In Node.js I was able to make a WordPress clone rather easily using the EventEmitter to replicate and build a hooks-system into the CMS core, which plugins could then attach to.
I now need this same level of extensibility and core isolation for my CMS written in and ported to Go. Basically I have the core finished now, but in order to make it truly flexible I have to be able to insert events (hooks) and to have plugins attach to these hooks with additional functionality.
I don't care about recompiling (dynamic / static linking), as long as you don't have to modify the core to load plugins - the CMS core should never be modified. (like WP, Drupal etc.)
I noticed there's a few rather unknown projects, trying to implement events in Go looking somewhat similar to EventEmitter in Node.js:
https://github.com/CHH/eventemitter
https://github.com/chuckpreslar/emission
Since those 2 projects above haven't gained much popularity and attention somehow I feel this way of thinking about events might now be how we should do it in Go? Does this mean Go is maybe not geared to this task? To make truly extendable applications through plugins?
Doesn't seem like Go has events built into its core, and RPC doesn't seem like a valid solution for integrating plugins into your core application as were they built in natively, and as if they were part of the main application itself.
What's the best way for seamless plugin integration into your core app, for unlimited extension points (in core) without manipulating core every time you need to hook up a new plugin?
In general, in Go, if you need events you probably need to use channels, but if you need plugins, the way to go is
interfaces. Here's a bit lengthy example of a simple plugin architecture that minimizes the code that needs to be
written in the app's main file to add plugins (this can be automated but not dnyamic, see below).
I hope it's in the direction you're looking for.
1. The Plugin Interfaces
So okay, let's say we have two plugins, Fooer and Doer. We first define their interfaces:
// All DoerPlugins can do something when you call that method
type DoerPlugin interface {
DoSomething()
}
// All FooerPlugins can Foo() when you want them too
type FooerPlugin interface {
Foo()
}
2. The Plugin Registry
Now, our core app has a plugin registry. I'm doing something quicky and dirty here, just to get the idea across:
package plugin_registry
// These are are registered fooers
var Fooers = []FooerPlugin{}
// Thes are our registered doers
var Doers = []DoerPlugin{}
Now we expose methods to add plugins to the registry. The simple way is to add one per type, but you could
go with more complex reflection stuff and have one function. But usually in Go, try to keep things simple :)
package plugin_registry
// Register a FooerPlugin
func RegisterFooer(f FooerPlugin) {
Fooers = append(Fooers, f)
}
// Register a DoerPlugin
func RegisterDoer(d DoerPlugin) {
Doers = append(Doers, d)
}
3. Implementing and registering a plugin
Now, suppose this is your plugin module. We create a plugin that is a doer, and in our package's
init() method we register it. init() happens once on program started for every imported package.
package myplugin
import (
"github.com/myframework/plugin_registry"
)
type MyPlugin struct {
//whatever
}
func (m *MyPlugin)DoSomething() {
fmt.Println("Doing something!")
}
Again, here is the "init magic" that registers the package automatically
func init() {
my := &MyPlugin{}
plugin_registry.RegisterDoer(my)
}
4. Importing the plugins registers them automatically
And now, the only thing we'll need to change is what we import into our main package. Since
Go doesn't have dynamic imports or linking, this is the only thing you'll need to write.
It's pretty trivial to create a go generate script that will generate a main file
by looking into the file tree or a config file and finding all the plugins you need to import.
It's not dynamic but it can be automated. Because main imports the plugin for the side effect of the registration, the import uses the blank identifier to avoid unused import error.
package main
import (
"github.com/myframework/plugin_registry"
_ "github.com/d00dzzzzz/myplugin" //importing this will automaticall register the plugin
)
5. In the app's core
And now our core app doesn't need any code to change to be able to interact with plugins:
func main() {
for _, d := range plugin_registry.Doers {
d.DoSomething()
}
for _, f := range plugin_registry.Fooers {
f.Foo()
}
}
And that's about it. Keep in mind that the plugin registry should be a separate package
that both the app's core and the plugins can import, so you won't have circular imports.
Of course you can add event handlers to this mix, but as I've demonstrated, it's not needed.

Get a CGImageRef from an IKImageView in Swift

I'm trying to get the image associated with a view but Xcode returns me an error. Here is the code:
#IBOutlet var imageView: IKImageView // Link to the image view.
func saveImage() {
var newImage: CGImageRef = imageView.image() // Line with an error.
}
I have a awakeFromNibfunction to set the image in the view. The code above returns me 'Unmanaged<CGImage>' is not convertible to 'CGImageRef'.
I know that types are optional in Swift but I don't understand why I get this error, the IKImageView Class Reference clearly said that get IKImageView's image return a CGImageRef.
This has to do with the bridging of Objective C APIs into Swift. The Swift versions of an API have been bridged (mostly automatically, I believe) by converting the original Objective C headers into a Swift version. In many cases, the conversion can work out what kind of value you're going to get back, and therefore in Swift you get a "normal" type that you can use as you'd expect.
However, the conversion is still a work in progress. In this case, what you're seeing is a type used to help you manage memory manually when the conversion process doesn't know whether you're going to get back an object that's been retained for you or not, because the API doesn't have the conventional annotation that it could use to figure it out.
The Unmanaged type effectively says "you have to tell me what to do with the contained value to get the memory management right". As soon as you get an Unmanaged value, you should call either one of its two methods:
takeUnretainedValue()
takeRetainedValue()
...depending on whether the object you get back was a "+0" or "+1". So, you're expected to do something like:
var newImage: CGImage = imageView.image().takeUnretainedValue()
And as soon as you've done that (which you should do pretty much immediately), you've given Swift enough of a hint that it can now appropriately manage the object correctly with ARC, and got yourself a valid reference of the right type.
This is touched on briefly at the end of the "Swift Interoperability in Depth" WWDC video from this year.
Look at the image() method in the new version of the IKImageView reference that shows Swift declarations and you'll see it returns an Unmanaged<CGImage>!. That means that ImageKit isn't set up such that the compiler can automatically infer CoreFoundation memory management semantics on its API. (This affects both Swift translation and ObjC implicit ARC bridging.) It should, so that'd be a good bug to file.
The Swift type Unmanaged<T> is how you interact with CF types when the compiler can't automatically infer their ARC behavior. To get at the underlying type, call either takeUnretainedValue() or takeRetainedValue() — you have to choose which depending on whether the API you're calling is known to already be retaining the value it returns. Exciting guesswork! (Again, eliminating guesswork would be a good bug to file.) You can read more about this in Using Swift with Cocoa and Objective-C.
Anyway, in this case you can probably expect that IKImageView is not incrementing the retain count of the CGImage when providing it, so you can unwrap the Unmanaged like so:
var newImage: CGImage = imageView.image().takeUnretainedValue()
Also note you don't need the Ref suffix when working with CF types in Swift. (CGImageRef is a typealias to CGImage.) Swift lets you work with CF types as if they're Swift objects (instead of as C pointers to opaque C types).

Resources