Ответ 1
Компилятор реализует вариативные аргументы, отбрасывая каждый аргумент объявленному вариационному типу, упаковывая их в Array
такого типа и передавая этот массив вариационной функции. В случае testWrapper
, заявленная VARIADIC тип CVarArg
, поэтому, когда testWrapper
называет logDefault
, это то, что происходит под одеялом: testWrapper
бросает 1.2345
к CVarArg
, создает Array<CVarArg>
, и передает его logDefault
в args
.
Затем logDefault
вызывает os_log
, передавая ему Array<CVarArg>
в качестве аргумента. Это ошибка в коде. Ошибка довольно тонкая. Проблема в том, что os_log
не принимает аргумент Array<CVarArg>
; os_log
сам по себе является вариационным по сравнению с CVarArg
. Таким образом, Swift выдает args
(Array<CVarArg>
) в CVarArg
и CVarArg
, что CVarArg
в другой Array<CVarArg>
. Структура выглядит так:
Array<CVarArg> created in 'logDefault'
|
+--> CVarArg (element at index 0)
|
+--> Array<CVarArg> (created in 'testWrapper')
|
+--> CVarArg (element at index 0)
|
+--> 1.2345 (a Double)
Затем logDefault
передает этот новый Array<CVarArg>
в os_log
. Итак, вы просите os_log
отформатировать свой первый элемент, который является (вроде) Array<CVarArg>
, используя %f
, что является бессмыслицей, и вы получаете 0.000000
качестве вывода. (Я говорю "своего рода", потому что здесь есть некоторые тонкости, которые я объясню позже).
Таким образом, logDefault
передает свой входящий Array<CVarArg>
как один из потенциально многих переменных параметров для os_log
, но то, что вы действительно хотите сделать logDefault
, это передать этот входящий Array<CVarArg>
как весь набор переменных параметров в os_log
, обертывая его. Это иногда называют "аргументация splatting" на других языках.
К сожалению, у Swift еще нет синтаксиса для аргументации splatting. Он обсуждался не раз в Swift-Evolution (например, в этой ветке), но на горизонте еще нет решения.
Обычным решением этой проблемы является поиск сопутствующей функции, которая принимает уже вложенные переменные аргументы как один аргумент. Часто компаньон добавляет v
в имя функции. Примеры:
-
printf
(variadic) иvprintf
(принимаетva_list
, C эквивалентArray<CVarArg>
) -
NSLog
(variadic) иNSLogv
(принимаетva_list
) -
-[NSString initWithFormat:]
(variadic) и-[NSString WithFormat:arguments:]
(принимаетva_list
)
Поэтому вы можете искать os_logv
. К сожалению, вы его не найдете. Нет документального компаньона для os_log
который принимает предварительно os_log
аргументы.
На данный момент у вас есть два варианта:
-
Откажитесь от обертывания
os_log
в своей собственной вариационной оболочке, потому что просто нет хорошего способа это сделать или -
Возьмите совет Камрана (в своем комментарии к своему вопросу) и используйте
%@
вместо%f
. Но учтите, что в вашей строке сообщения может быть только один%@
(и других спецификаторов формата), потому что вы передаете только один аргументos_log
. Результат выглядит следующим образом:2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: ( "1.2345" )
Вы также можете подать радары запроса на повышение по адресу https://bugreport.apple.com с просьбой о функции os_logv
, но вы не должны ожидать, что она будет реализована в ближайшее время.
Так что это. Сделайте одну из этих двух вещей, возможно, подайте радар и продолжайте свою жизнь. Шутки в сторону. Перестань читать здесь. После этой линии ничего хорошего нет.
Хорошо, ты продолжал читать. Пусть заглянет под капот os_log
. Оказывается, реализация функции Swift os_log
является частью общедоступного исходного кода Swift:
@_exported import os @_exported import os.log import _SwiftOSOverlayShims @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) public func os_log( _ type: OSLogType, dso: UnsafeRawPointer = #dsohandle, log: OSLog = .default, _ message: StaticString, _ args: CVarArg...) { guard log.isEnabled(type: type) else { return } let ra = _swift_os_log_return_address() message.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in // Since dladdr is in libc, it is safe to unsafeBitCast // the cstring argument type. buf.baseAddress!.withMemoryRebound( to: CChar.self, capacity: buf.count ) { str in withVaList(args) { valist in _swift_os_log(dso, ra, log, type, str, valist) } } } }
Так получается, есть версия os_log
, называется _swift_os_log
, который принимает заранее аргументы в комплекте. withVaList
Swift использует withVaList
(который документирован) для преобразования Array<CVarArg>
в va_list
и передает его на _swift_os_log
, который сам также является частью открытого исходного кода Swift. Я не буду указывать здесь свой код, потому что он длинный, и нам на самом деле не нужно смотреть на него.
Во всяком случае, хотя это не задокументировано, мы можем на самом деле вызвать _swift_os_log
. Мы можем в основном скопировать исходный код os_log
и включить его в вашу функцию logDefault
:
func logDefaultHack(_ message: StaticString, dso: UnsafeRawPointer = #dsohandle, _ args: CVarArg...) {
let ra = _swift_os_log_return_address()
message.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in
buf.baseAddress!.withMemoryRebound(to: CChar.self, capacity: buf.count) { str in
withVaList(args) { valist in
_swift_os_log(dso, ra, .default, .default, str, valist)
}
}
}
}
И это работает. Тестовый код:
func testWrapper() {
logDefault("WTF: %f", 1.2345)
logDefault("WTF: %@", 1.2345)
logDefaultHack("Hack: %f", 1.2345)
}
Выход:
2018-06-20 02:22:56.131875-0500 test[39313:6086331] WTF: 0.000000
2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
"1.2345"
)
2018-06-20 02:22:56.132807-0500 test[39313:6086331] Hack: 1.234500
Я рекомендую это решение? Нет. Ад. Внутренние os_log
являются деталями реализации и могут изменяться в будущих версиях Swift. Поэтому не полагайтесь на них так. Но в любом случае интересно смотреть под обложки.
Одна последняя вещь. Почему компилятор не жалуется на преобразование Array<CVarArg>
в CVarArg
? И почему предложение Kamran (использования %@
) работает?
Оказывается, эти вопросы имеют один и тот же ответ: это потому, что Array
"мостик" для объекта Objective-C. В частности:
-
Foundation (на платформах Apple) делает
Array
совместимым с протоколом_ObjectiveCBridgeable
. Он реализует перемычкуArray
Objective-C, возвращаяNSArray
. -
Фонд также позволяет
Array
соответствовать протоколуCVarArg
. -
Функция
withVaList
запрашивает каждыйCVarArg
для преобразования себя в_cVarArgEncoding
. -
По умолчанию реализация
_cVarArgEncoding
для типа, который соответствует как_ObjectiveCBridgeable
иCVarArg
, возвращает объект Object-C сCVarArg
. -
Соответствие
Array
CVarArg
означает, что компилятор не будет жаловаться (молча) на преобразованиеArray<CVarArg>
вCVarArg
и вставлять его в другойArray<CVarArg>
.
Это бесшумное преобразование, вероятно, часто является ошибкой (как это было в вашем случае), поэтому было бы разумно, чтобы компилятор предупредил об этом и позволил вам отключить предупреждение с явным приложением (например, args as CVarArg
). Вы можете отправить отчет об ошибке на https://bugs.swift.org, если хотите.