Bundle.classNamed (_ className: String) завершается ошибкой, но NSClassFromString () работает - PullRequest
1 голос
/ 26 сентября 2019

Я работаю над проверкой концепции загрузки плагина из хост-приложения в Swift.Я могу загрузить фреймворк или пакет в класс Bundle, NSClassFromString и Bundle.principalClass, кажется, работают нормально, но я не могу получить какой-либо результат от функции Bundle.classNamed.

Это фрагмент кода, который я использую:

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

Любые намеки / отзывы приветствуются!

1 Ответ

1 голос
/ 26 сентября 2019

Кажется, что bundle.classNamed() не работает, если пакет был загружен по Bundle(path:) и относительно пути.Вот минимальный автономный пример, демонстрирующий проблему:

// 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)")

В качестве альтернативы можно работать с URL , вот необходимые изменения:

// 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 { $0.pathExtension == "bundle"
            || $0.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

Примечание:

Классы плагинов не должны наследоваться от NSObject, если вам требуется метод init() вprotocol PluginInterface (и внедрить required init() во всех плагинах) и загрузить плагины следующим образом

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()
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...