我可以用Swift编写Spotlight导入程序吗?

vbkedwbf  于 2022-12-17  发布在  Swift
关注(0)|答案(3)|浏览(118)

我需要为我用Swift编写的应用程序编写一个Spotlight导入程序,我指的是苹果官方的Writing a Spotlight Importer指南。
这看起来很简单,但是创建一个Spotlight导入器项目会创建一个Objective-C实现的默认设置。(我过去用过很多次)但是我为我的应用程序编写的所有内容都是Swift,所以我真的很想也用Swift编写导入程序,以避免在语言之间切换,这样我就可以分享一些我已经完成的阅读文件的代码。
首先,是否可以使用Swift而不是Objective-C来编写Spotlight导入器?如果可以,我应该从哪里开始(例如,如果我从Objective-C开始,我应该怎么做才能切换到Swift)?

i86rm4rw

i86rm4rw1#

我花了点时间才让它起作用。
我没有将Swift代码添加到mdimporter,而是导入了一个已经为我的应用设置好的嵌入式框架。
除了main.cGetMetadataForFile.m之外,我删除了所有示例代码。在后一个示例中,我导入了我的框架,其中所有功能现在都作为Swift代码驻留。
生成的mdimporter将添加到应用程序。在文件检查器中,将位置设置为相对于生成产品
然后,应用程序添加mdimporter,并执行复制文件构建阶段。

  • 目的地:** Package 器**
  • 子路径:内容/库/聚光灯

以下内容需要添加到运行搜索路径构建设置中,因为我们要链接到应用的嵌入式框架。

@loader_path/../../../../../Frameworks

如果您在构建应用时遇到无法找到框架模块的编译器错误(具体取决于工作区的设置方式),您可能需要修改应用的Scheme

  • 关闭并行生成

1.在此序列中添加Build目标:
1.框架项目

  1. mdimporter项目
    1.应用程序项目
    将所有逻辑放在一个框架中的额外好处是,它可以在Playground中原型化和验证,比调试mdimporter插件容易一百万倍。
xwbd5t1u

xwbd5t1u2#

是的,可以完全 * 在Swift中编写Spotlight导入程序!

  • main.m中的几行代码除外

我刚刚在这里发表了一篇文章:https://github.com/foxglove/MCAPSpotlightImporter
下面是一篇关于实施过程的详细博客文章:https://foxglove.dev/blog/implementing-a-macos-search-plugin-for-robotics-data
困难的部分是实现一个与CFPlugIn架构兼容的插件。(MDImporter特定的逻辑相对最少。)CFPlugIn API基于Microsoft的COM,而Apple的文档已有近20年的历史。
插件应该是一个符合特定内存布局的内存块--具体来说,块中的第一个值必须是指向所请求接口(对于MDImporter,这是MDImporterInterfaceStructMDImporterURLInterfaceStruct)或基本IUnknown接口的虚函数表(vtable)的指针。
我想把Swift代码组织成一个类,但是你不能控制Swift类示例的内存布局,所以我创建了一个“wrapper”内存块来保存vtable和一个指向类示例的unsafe指针,这个类有一个static func allocate(),它使用UnsafeMutablePointer来分配wrapper块,在其中创建和存储类示例,并且初始化vtable。
vtable实现标准的COM基本接口(IUnknown)函数(QueryInterfaceAddRefRelease),方法是从 Package 器中获取类示例,并在示例上调用queryInterface()addRef()release()方法。(或ImporterImportData)。不幸的是,在我的测试中,Spotlight似乎没有将指向 Package 器结构体的正确指针作为ImporterImportURLData的第一个参数传递,因此不可能调用类示例上的方法,因此,实际导入文件属性的函数必须是全局函数,由于这个原因,我无法使插件实现成为一个可以与任何接口一起使用的更通用的类-它必须与具体的全球进口商职能挂钩。
我鼓励您查看完整的on GitHub源代码,但为了不只是提供链接,下面是核心功能:

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
  }
}

最后,我创建了一个@objc类,它将allocate函数公开给Obj-C,在Obj-C中,我可以从main.m调用它,并从工厂函数返回指向 Package 器块的指针,这是必要的,因为我不想使用不稳定的@_cdecl属性将Swift函数直接公开给插件加载器。
x一个一个一个一个x一个一个二个x
有关详细信息,请参见my blog post

9wbgstp7

9wbgstp73#

由于苹果引入了Swift作为一种语言,它与任何现有的Objective-C项目完美兼容,我建议你从任何对你来说更容易的东西开始。
如果你最了解Swift,那么没有什么能阻止你使用它--无论你想做什么项目。如果你想学习一个为Objective-C编写的教程,而且还没有为Swift更新,我想你有 * 两个选择 *(我个人建议现在选择第二个选项):
1.现在就用Swift从头开始编写相同的逻辑(几乎所有在Objective-C中可以实现的东西在Swift中也可以实现)。为此,你需要理解Objective-C的基础知识以及Swift中相应的语法和特性。
1.从Objective-C开始学习教程,并在开始时让事情变得更简单(不需要真正理解教程的细节)。然后利用Swift代码与Objective-C代码混合匹配的巨大可能性,根据您的需要定制代码或使用您自己预先存在的类扩展代码。

更具体地说,关于第二个备选方案:

如果你想编写新的类只需要使用Swift --你完全可以在Swift中使用所有用Objective-C编写的东西,反之亦然。如果你觉得你需要修改已经用Objective-C编写的类,你有以下选择:用一个 * 新的Swift类 * 扩展用Objective-C编写的类,*在Swift重写该特定文件,或者直接编辑Objective-C文件。
了解更多**如何将Swift代码与Objective-C混合搭配,我推荐阅读苹果official documentation。这是苹果工程师为开发人员编写的免费iBook“使用Swift与可可和Objective-C”的一部分。
不幸的是,苹果似乎确实提供了一个Spotlight导入器的模板,目前只针对Objective-C。不知道为什么--我看不出有什么能阻止他们支持Swift。我们可能应该用苹果的Bug Reporter报告这个,以强调人们实际上是在要求这个。
希望我没有忽略任何东西,否则我的回答将毫无意义。

UPDATE(request)以下是有关从何处开始实施第一种方法的一些步骤:

  • 首先,使用最新的XCode版本创建一个Spotlight Importer项目-创建一个新的“可可Touch”类,名称与您预先创建的主Objective-C类完全相同(例如,“MySpotlight Importer”)
  • 选择Swift并在类创建期间询问时**“创建桥接标头”-重新实现Swift类中ObjC-MySpotlight Importer类编写的代码(你可能想在Swift和Objective-C中创建一个支持Core Data的可可应用程序,以了解它们的不同之处)-我不确定你是否也能在Swift中重写GetMetaDataFile.m**,在我的测试中,我无法找出这一点,因此您可能需要保留它(目前)-如果您在此过程中收到任何错误沿着指出某些缺少的配置,只需在项目“Build settings”中搜索相关文件/类,并在那里应用您的更改

我希望这能帮助你入门,而且足够具体。我试着自己做必要的修改,以便在Swift中提供一个示例项目,但不幸的是,我无法在有限的时间内让它工作。你可能需要考虑公开提供你的代码(例如在GitHub上发布一个链接),以防你决定自己移植它,这样其他人也可以从中受益。
祝你好运!

相关问题