创建一个基于 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
,并且实现run
和view
方法。
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)")
}
}
}
为了能够运行,我们需要在 Xcode 中勾选关闭Library Validation
,否则会报错Library not loaded: @rpath/libswiftCore.dylib
。