Skip to main content

创建一个基于 Swift 的插件系统

在现在,越来越多的软件都拥有着成熟的插件系统,这些插件系统可以让用户自定义软件的行为,比如,你可以在你的博客系统中安装一个插件,这个插件可以让你的博客系统支持 Markdown 语法,这样你就可以在你的博客中使用 Markdown 语法来书写文章了。而 Swift 也是可以创建插件系统的,这篇文章将会教你如何创建一个基于 Swift 的插件系统。

什么是插件系统?

插件系统是一种软件架构,它允许用户在不修改软件本身的情况下,为软件添加新的功能。插件系统通常是通过插件来实现的,插件是一种可以被加载到软件中的代码,插件可以为软件添加新的功能,比如,一个插件可以为软件添加一个新的命令,或者为软件添加一个新的命令行选项,也可以是创建一个全新的 UI。VS Code 就是一个插件系统,它允许用户为 VS Code 添加新的功能,比如,你可以为 VS Code 添加一个新的语言支持,或者添加一个新的代码片段。

创建一个插件系统

创建一个插件 interface

这个 interface 是全部插件需要实现的模版,里面定义了比较通用的插件接口,比如,插件的名字,插件的版本,插件的作者,插件的描述等等。 我们这里会定义一个简单的插件interface, 里面有一个可供执行的run, 以及渲染 UI 的view

public protocol PluginInterfaceProtocol {
associatedtype Body: View

func run() -> String

@ViewBuilder @MainActor var view: Self.Body { get }
}

创建一个插件 Builder

我们需要一个Builder来创建我们的插件,这个Builder需要继承自PluginBuilder,并且需要实现build方法,这个方法会返回一个PluginInterfaceProtocol的实例。

open class PluginBuilder<T> {
public init() {}

open func build() -> any PluginInterfaceProtocol {
fatalError("You should override this method.")
}
}

创建一个插件

我们的插件也很简单,它需要继承自PluginInterfaceProtocol,并且实现runview方法。

struct PluginA: PluginInterfaceProtocol {
func run() -> String {
return "Calling from test plugin"
}

var view: some View {
Text("Hello world")
}
}

创建一个插件工厂

我们需要一个工厂来创建我们的插件,这个工厂需要继承自PluginBuilder,并且需要实现build方法,这个方法会返回一个PluginInterfaceProtocol的实例。

@_cdecl("createPlugin")
public func createPlugin() -> UnsafeMutableRawPointer {
return Unmanaged.passRetained(PluginABuilder()).toOpaque()
}

final class PluginABuilder: PluginBuilder<Any> {
override func build() -> any PluginInterfaceProtocol {
PluginA()
}
}

构建

运行swift build -c release,会在.build/release目录下生成一个libPluginA.dylib的动态库,这个动态库就是我们的插件。

使用插件系统

下面这个函数就会将我们定义的插件加载到内存中,并且返回一个PluginInterfaceProtocol的实例。

func plugin(at path: String) -> any PluginInterfaceProtocol {
let openRes = dlopen(path, RTLD_NOW|RTLD_LOCAL)
if openRes != nil {
defer {
dlclose(openRes)
}

let symbolName = "createPlugin"
let sym = dlsym(openRes, symbolName)

if sym != nil {
let f: InitFunction = unsafeBitCast(sym, to: InitFunction.self)
let pluginPointer = f()
let builder = Unmanaged<PluginBuilder<Any>>.fromOpaque(pluginPointer).takeRetainedValue()
return builder.build()
}
else {
fatalError("error loading lib: symbol \(symbolName) not found, path: \(path)")
}
}
else {
if let err = dlerror() {
fatalError("error opening lib: \(String(format: "%s", err)), path: \(path)")
}
else {
fatalError("error opening lib: unknown error, path: \(path)")
}
}
}
note

为了能够运行,我们需要在 Xcode 中勾选关闭Library Validation,否则会报错Library not loaded: @rpath/libswiftCore.dylibLibrary Validation