Проверка во время выполнения на наличие слабосвязанных символов из стороннего фреймворка в Swift

В macOS я использую внешнюю среду (написанную на C), которая должна быть установлена ​​пользователем. В Swift мне нужно во время выполнения проверять, существует ли он, и я не могу использовать #available(), поскольку он предназначен для функций, связанных с ОС, и я пытаюсь отследить внешнюю среду. Кроме того, NSClassFromString() бесполезен, так как это не среда Objective-C.

Я пытался понять, как скопировать эквивалент Objective C для проверки слабо связанного символа, такого как:

if ( anExternalFunction == NULL ) {
    // fail graciously
} else {
    // do my thing
}

но в Swift это, похоже, не работает: компилятор утверждает, что поскольку функция anExternalFunction не является обязательной, я всегда получаю!= nil, что имеет смысл "Swift", но не помогает мне ни на секунду.

Я нашел два решения, но они воняют мой код, как вы не поверите:

Вариант 1, создать файл Objective-C с функцией isFrameworkAvailable(), выполняющей работу и вызывающей из Swift

Вариант 2, на самом деле проверка библиотеки с помощью следующего кода Swift:

let libHandle = dlopen("/Library/Frameworks/TheLib.framework/TheLib", RTLD_NOW)

if (libHandle != nil) {
    if dlsym(libHandle, "anExternalFunction") != nil {
        return true
    }
}
return false

Мне не удалось заставить вариант 2 хорошо работать с RTLD_DEFAULT, поскольку по какой-то причине он определен в dlfcn.h (-2), но, похоже, не импортируется в Swift (как все отрицательные указатели в этом заголовке: RTLD_NEXT, RTLD_DEFAULT RTLD_SELF, RTLD_MAIN_ONLY). Я нашел этот самый уродливый хак, чтобы заставить его работать:

if dlsym(unsafeBitCast(-2, to: UnsafeMutableRawPointer.self), "anExternalFunction") != nil {
    // library installed
}

Так что для варианта 2 мне нужен путь или хак, который я нахожу особенно уродливым (но он работает), а вариант 1 не очень "быстр" и не имеет для меня никакого смысла: как правильно делать это в Swift?

Редактирует: уточнил вопрос, пояснил Вариант 2 лучше.

0 ответов

Я нашел два способа сделать это:

    1.
if let _ = dlopen("/Library/Frameworks/MyLibrary.framework/MyLibrary", RTLD_NOW) {
  print("Can open my library")
}
    2.
#if canImport(MyLibrary)
  print("Can import my library")
#endif

Второй вариант кажется явно лучше с точки зрения удобочитаемости и безопасности типов, но я не уверен, каковы компромиссы между ними.

Другие вопросы по тегам