Bundle.classNamed(_ className: String) 失败但 NSClassFromString() 工作
Bundle.classNamed(_ className: String) fails but NSClassFromString() work
我正在研究从 Swift 中的主机应用程序加载插件的概念证明。我能够在 Bundle class、NSClassFromString
和 Bundle.principalClass
上加载一个框架或一个 bundle 似乎工作正常,但我无法从 [=15 获得任何结果=]函数。
这是我正在使用的代码片段:
for fullName in bundles {
print("Loading framework: \(fullName)")
if let bundle = Bundle(path: fullName), bundle.load(),
let name = fullName.split(separator: ".").first {
let typeNamed = bundle.classNamed(name + ".Plugin") as? NSObject.Type
let typeNS = NSClassFromString(name + ".Plugin") as? NSObject.Type
let typeNamedV2 = bundle.classNamed(name + ".PluginV2") as? NSObject.Type
let typeNSV2 = NSClassFromString(name + ".PluginV2") as? NSObject.Type
let typePrincipal = bundle.principalClass as? NSObject.Type
print("From bundle.classNamed: \(initPlugin(from: typeNamed)?.description ?? "")" )
print("From NSClassFromString: \(initPlugin(from: typeNS)?.description ?? "")" )
print("From bundle.classNamed: \(initPlugin(from: typeNamedV2)?.description ?? "")" )
print("From NSClassFromString: \(initPlugin(from: typeNSV2)?.description ?? "")" )
print("From bundle.principalClass: \(initPlugin(from: typePrincipal)?.description ?? "")" )
bundle.unload()
}
}
输出如下:
Loading framework: One.framework
From bundle.classNamed:
From NSClassFromString: <One.Plugin: 0x102802060>
From bundle.classNamed:
From NSClassFromString: <One.PluginV2: 0x102802060>
From bundle.principalClass: <One.Plugin: 0x102801650>
Loading framework: CommonInterface.framework
From bundle.classNamed:
From NSClassFromString:
From bundle.classNamed:
From NSClassFromString:
From bundle.principalClass:
Loading framework: Bundle.bundle
From bundle.classNamed:
From NSClassFromString: <Bundle.Plugin: 0x102b08040>
From bundle.classNamed:
From NSClassFromString: <Bundle.PluginV2: 0x102b08f80>
From bundle.principalClass: <Bundle.Plugin: 0x102801ea0>
项目配置为 Swift 5 和 Xcode 11 楼。
在这里你可以找到 POC 的完整源代码:
https://github.com/lechuckcaptain/SwiftPluginArchitectureExample
欢迎任何hint/feedback!
如果通过 Bundle(path:)
和 relative 路径加载包,bundle.classNamed()
似乎不起作用。这是一个演示问题的最小独立示例:
// This works:
let b1 = Bundle(path: "/System/Library/Frameworks/Foundation.framework")!
print(b1.load()) // true
let c1: AnyClass? = b1.classNamed("NSString")
print(c1 as Any) // Optional(NSString)
// This does not work
FileManager.default.changeCurrentDirectoryPath("/System/Library/Frameworks")
let b2 = Bundle(path: "Foundation.framework")!
print(b2.load()) // true
let c2: AnyClass? = b2.classNamed("NSString")
print(c2 as Any) // nil
我觉得这像是一个错误。在您的情况下,快速解决方法是通过绝对路径加载包:
if let bundle = Bundle(path: "\(path)/\(fullName)")
或者使用 URLs,这里是必要的修改:
// main.swift:
let pluginHost = PluginHost()
pluginHost.loadPlugins(at: Bundle.main.bundleURL)
// PluginHost.swift:
func loadPlugins(at url: URL) {
let fileManager = FileManager.default
let bundles = (try? fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
.filter { [=12=].pathExtension == "bundle"
|| [=12=].pathExtension == "framework"
}) ?? []
for bundleURL in bundles {
print("Loading framework: \(bundleURL)")
if let bundle = Bundle(url: bundleURL) {
let bundleName = bundleURL.deletingPathExtension().lastPathComponent
print("Name:", bundleName)
let typeNamed = bundle.classNamed(bundleName + ".Plugin") as? NSObject.Type
let typeNamedV2 = bundle.classNamed(bundleName + ".PluginV2") as? NSObject.Type
print("From bundle.classNamed: \(initPlugin(from: typeNamed)?.description ?? "")" )
print("From bundle.classNamed V2: \(initPlugin(from: typeNamedV2)?.description ?? "")" )
}
}
}
输出:
Start plugin loading
Loading framework: file:///.../One.framework/
Name: One
From bundle.classNamed: <One.Plugin: 0x100537f90>
From bundle.classNamed V2: <One.PluginV2: 0x1005388a0>
Loading framework: file:///.../CommonInterface.framework/
Name: CommonInterface
From bundle.classNamed:
From bundle.classNamed V2:
Loading framework: file:///.../Bundle.bundle/
Name: Bundle
From bundle.classNamed: <Bundle.Plugin: 0x10053a1d0>
From bundle.classNamed V2: <Bundle.PluginV2: 0x10053a1d0>
End plugin loading
Program ended with exit code: 0
备注:
如果您需要 protocol PluginInterface
中的 init()
方法(并实现 required init()
,则插件 类 不必继承自 NSObject
在所有插件中)并像这样加载插件
if let cls = bundle.classNamed(bundleName + ".Plugin") as? PluginInterface.Type {
let plugin = cls.init()
plugin.doSomething()
}
if let cls = bundle.classNamed(bundleName + ".PluginV2") as? PluginInterface.Type {
let plugin = cls.init()
plugin.doSomething()
}
我正在研究从 Swift 中的主机应用程序加载插件的概念证明。我能够在 Bundle class、NSClassFromString
和 Bundle.principalClass
上加载一个框架或一个 bundle 似乎工作正常,但我无法从 [=15 获得任何结果=]函数。
这是我正在使用的代码片段:
for fullName in bundles {
print("Loading framework: \(fullName)")
if let bundle = Bundle(path: fullName), bundle.load(),
let name = fullName.split(separator: ".").first {
let typeNamed = bundle.classNamed(name + ".Plugin") as? NSObject.Type
let typeNS = NSClassFromString(name + ".Plugin") as? NSObject.Type
let typeNamedV2 = bundle.classNamed(name + ".PluginV2") as? NSObject.Type
let typeNSV2 = NSClassFromString(name + ".PluginV2") as? NSObject.Type
let typePrincipal = bundle.principalClass as? NSObject.Type
print("From bundle.classNamed: \(initPlugin(from: typeNamed)?.description ?? "")" )
print("From NSClassFromString: \(initPlugin(from: typeNS)?.description ?? "")" )
print("From bundle.classNamed: \(initPlugin(from: typeNamedV2)?.description ?? "")" )
print("From NSClassFromString: \(initPlugin(from: typeNSV2)?.description ?? "")" )
print("From bundle.principalClass: \(initPlugin(from: typePrincipal)?.description ?? "")" )
bundle.unload()
}
}
输出如下:
Loading framework: One.framework
From bundle.classNamed:
From NSClassFromString: <One.Plugin: 0x102802060>
From bundle.classNamed:
From NSClassFromString: <One.PluginV2: 0x102802060>
From bundle.principalClass: <One.Plugin: 0x102801650>
Loading framework: CommonInterface.framework
From bundle.classNamed:
From NSClassFromString:
From bundle.classNamed:
From NSClassFromString:
From bundle.principalClass:
Loading framework: Bundle.bundle
From bundle.classNamed:
From NSClassFromString: <Bundle.Plugin: 0x102b08040>
From bundle.classNamed:
From NSClassFromString: <Bundle.PluginV2: 0x102b08f80>
From bundle.principalClass: <Bundle.Plugin: 0x102801ea0>
项目配置为 Swift 5 和 Xcode 11 楼。
在这里你可以找到 POC 的完整源代码:
https://github.com/lechuckcaptain/SwiftPluginArchitectureExample
欢迎任何hint/feedback!
如果通过 Bundle(path:)
和 relative 路径加载包,bundle.classNamed()
似乎不起作用。这是一个演示问题的最小独立示例:
// This works:
let b1 = Bundle(path: "/System/Library/Frameworks/Foundation.framework")!
print(b1.load()) // true
let c1: AnyClass? = b1.classNamed("NSString")
print(c1 as Any) // Optional(NSString)
// This does not work
FileManager.default.changeCurrentDirectoryPath("/System/Library/Frameworks")
let b2 = Bundle(path: "Foundation.framework")!
print(b2.load()) // true
let c2: AnyClass? = b2.classNamed("NSString")
print(c2 as Any) // nil
我觉得这像是一个错误。在您的情况下,快速解决方法是通过绝对路径加载包:
if let bundle = Bundle(path: "\(path)/\(fullName)")
或者使用 URLs,这里是必要的修改:
// main.swift:
let pluginHost = PluginHost()
pluginHost.loadPlugins(at: Bundle.main.bundleURL)
// PluginHost.swift:
func loadPlugins(at url: URL) {
let fileManager = FileManager.default
let bundles = (try? fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
.filter { [=12=].pathExtension == "bundle"
|| [=12=].pathExtension == "framework"
}) ?? []
for bundleURL in bundles {
print("Loading framework: \(bundleURL)")
if let bundle = Bundle(url: bundleURL) {
let bundleName = bundleURL.deletingPathExtension().lastPathComponent
print("Name:", bundleName)
let typeNamed = bundle.classNamed(bundleName + ".Plugin") as? NSObject.Type
let typeNamedV2 = bundle.classNamed(bundleName + ".PluginV2") as? NSObject.Type
print("From bundle.classNamed: \(initPlugin(from: typeNamed)?.description ?? "")" )
print("From bundle.classNamed V2: \(initPlugin(from: typeNamedV2)?.description ?? "")" )
}
}
}
输出:
Start plugin loading
Loading framework: file:///.../One.framework/
Name: One
From bundle.classNamed: <One.Plugin: 0x100537f90>
From bundle.classNamed V2: <One.PluginV2: 0x1005388a0>
Loading framework: file:///.../CommonInterface.framework/
Name: CommonInterface
From bundle.classNamed:
From bundle.classNamed V2:
Loading framework: file:///.../Bundle.bundle/
Name: Bundle
From bundle.classNamed: <Bundle.Plugin: 0x10053a1d0>
From bundle.classNamed V2: <Bundle.PluginV2: 0x10053a1d0>
End plugin loading
Program ended with exit code: 0
备注:
如果您需要 protocol PluginInterface
中的 init()
方法(并实现 required init()
,则插件 类 不必继承自 NSObject
在所有插件中)并像这样加载插件
if let cls = bundle.classNamed(bundleName + ".Plugin") as? PluginInterface.Type {
let plugin = cls.init()
plugin.doSomething()
}
if let cls = bundle.classNamed(bundleName + ".PluginV2") as? PluginInterface.Type {
let plugin = cls.init()
plugin.doSomething()
}