Из комментария @MartinR и ссылки на NSLog недоступен вы знаете, что Swift может вызывать (Objective-) функции и методы C, которые принимают va_list
аргументы, используя типы Swift CVarArg
иCVaListPointer
.
Многие из общих (Objective-) C-переменных функций и методов имеют одноуровневый элемент, который занимает va_list
, поэтому эта поддержка в Swift обеспечивает доступ к ним.
Мне также нужно вызвать этот метод swift в файле target-c xxx.m.
Однако вы хотите пойти другим путем и написали версию функции Swift с переменным числом аргументов для вашего Objective-Cметод, который вы нашли, вы не могли бы его назвать.Вы пытались спросить, каково было решение, Как вы называете метод Swift Variadic из Objective-C? , косвенный ответ (ваш вопрос был отмечен как дубликат) на этот вопрос дает подсказку - использоватьмассив - но не обрабатывает универсальность, необходимую для сценария с форматированным типом печати.Посмотрим, сможем ли мы туда добраться ...
(При использовании Xcode 10 / Swift 4.2 любая другая версия Swift, вероятно, отличается.)
Мы будем использоватьследующий класс Swift в качестве основы:
class SwiftLog : NSObject
{
// Swift entry point
static func Log(_ format : String, args : CVarArg...)
{
withVaList(args) { LogV(format, $0)}
}
// Shared core
private static func LogV(_ format : String, _ args: CVaListPointer)
{
NSLogv(format, args)
}
}
Это предоставляет Swift функцию с переменным числом аргументов, которая будет принимать все типы библиотек Swift, которые вам, вероятно, интересны, и еще несколько, которых вы не знаете (3287 перечислены в * 1026).* Apple CVarArg
документация ).Функция частного ядра здесь тривиальна, вы, вероятно, захотите сделать что-то более сложное.
Теперь вы хотите вызвать Log()
из Objective-C, но, как вы обнаружили, вы не можете из-заCVarArg
.Однако Objective-C может вызывать функции Swift, которые принимают NSObject
аргументы, а NSObject
реализует CVarArg
, что приводит нас к нашей первой попытке:
// Objective-C entry point
@objc static func Log(_ format : String, args : [NSObject])
{
withVaList(args) { LogV(format, $0) }
}
Это работает как есть, но каждый аргумент долженбыть объектом и отформатированным в %@
, переключение на Objective-C:
[SwiftLog LogObjects:@"%@|%@|%@|%@|%@|%@" args:@[@"42", @4.2, @"hello", @31, @'c', NSDate.new]];
производит:
42|4.2|hello|31|99|Sun Nov 11 08:47:35 2018
Это работает в определенных пределах, мы потерялигибкость форматирования - нет %6.2f
, %x
и т. д. - и символ получился как 99
.
Можем ли мы улучшить его?Если вы готовы пожертвовать способностью печатать NSNumber
значений как есть, тогда да.В Swift измените функцию Log()
на:
@objc static func Log(_ format : String, args : [NSObject])
{
withVaList(args.map(toPrintfArg)) { LogV(format, $0) }
}
Пропуск toPrintfArg
на данный момент (это просто большой и некрасивый) в Objective-C, мы можем назвать эту версию как:
[SwiftLog Log:@"%@|%4.2f|%10s|%x|%c|%@" args:@[@"42", @4.2, @((intptr_t)"hello"), @31, @'c', NSDate.new]];
, который выдает:
42|4.20| hello|1f|c|Sun Nov 11 08:47:35 2018
Гораздо лучше, и персонаж правильный.Так что же делает toPrintfArg
? 1066 *
В приведенном выше примере мы должны были передать массив объектов в Swift и сделать все примитивные значения обернутыми в NSNumber
объекты.
В Objective-C объект NSNumber
не раскрывает многое о том, что он переносит, методы доступа (.doubleValue
, .integerValue
и т. Д.) Будут преобразовывать независимо от значения переносабыло в значение запрошенного типа и вернуть его.
Однако NSNumber
"соединен бесплатно" с базовыми типами Foundation CFBoolean
и CFNumber
;первый из них предназначен для логических значений (очевидно!), а второй - для всех других числовых типов и, в отличие от NSNumber
, предоставляет функцию, которая возвращает тип упакованного значения, поэтому его можно развернуть без преобразования.Используя эту информацию, мы можем извлечь исходные значения (эксперты, да, см. Ниже) значений из NSNumber
объектов, все эти извлеченные значения в Swift будут реализованы CVarArg
, вот так:
private static func toPrintfArg(_ item : NSObject) -> CVarArg
{
if let anumber = item as? NSNumber
{
if type(of:anumber) == CFBoolean.self { return anumber.boolValue }
switch CFNumberGetType(anumber)
{
case CFNumberType.sInt8Type: return anumber.int8Value
case CFNumberType.sInt16Type: return anumber.int16Value
case CFNumberType.sInt32Type: return anumber.int32Value
case CFNumberType.sInt64Type: return anumber.int64Value
case CFNumberType.float32Type: return Float32(anumber.floatValue)
case CFNumberType.float64Type: return Float64(anumber.doubleValue)
case CFNumberType.charType: return CChar(anumber.int8Value)
case CFNumberType.shortType: return CShort(anumber.int16Value)
case CFNumberType.intType: return CInt(anumber.int32Value)
case CFNumberType.longType: return CLong(anumber.int64Value)
case CFNumberType.longLongType: return CLongLong(anumber.int64Value)
case CFNumberType.floatType: return anumber.floatValue
case CFNumberType.doubleType: return anumber.doubleValue
case CFNumberType.cfIndexType: return CFIndex(anumber.int64Value)
case CFNumberType.nsIntegerType: return NSInteger(anumber.int64Value)
case CFNumberType.cgFloatType: return CGFloat(anumber.doubleValue)
}
}
return item;
}
Эта функция будет разворачивать (эксперты, да, большинство, см. Ниже) NSNumber
объекты к исходному типу значения, оставляя все другие объекты, как это должно быть отформатировано с помощью %@
(какпоказанный объектами NSString
и NSDate
в примере).
Надеюсь, это поможет, по крайней мере, больше, чем это смущает!Примечания для любопытных / экспертов следуют.
Примечания и предостережения
Сохранение указателей C
InВ приведенном выше примере строка C "hello"
была передана путем преобразования ее в intptr_t
, целочисленный тип C того же размера, что и указатель, а не как значение указателя. В этом контексте это нормально, va_list
- это, по сути, нетипизированный байт байтов, и формат сообщает NSLogv()
, какому типу интерпретировать следующие байты, преобразование в intptr_t
сохраняет те же байты / биты и позволяет указателю быть обернутым как NSNumber
.
Однако, если вашему приложению нужен фактический указатель на стороне Swift, вы можете вместо этого обернуть строку C в NSValue
:
[NSValue valueWithPointer:"hello"]
и разверните его в toPrintfArg
, добавив:
if let ptr = (item as? NSValue)?.pointerValue
{
return ptr.bindMemory(to: Int8.self, capacity: 1)
}
Это создает значение типа UnsafeMutablePointer<Int8>
, которое реализует CVarArg
(и, как последнее, capacity
не имеет значения).
Вы всегда получаете один и тот же тип назад?
Если вы оберните тип C как NSNumber
, а затем развернете его, как указано выше, может ли тип измениться из-за продвижения аргумента (что означает, что целочисленные типы, меньшие int
, будут переданы как int
значения, float
значения как double
) в C? Ответ: возможно , но обязательный тип для значения CVarArg
является продвинутым типом, поэтому он не должен иметь никакого значения в этом контексте - тип развернутого значения соответствует ожидаемый спецификатор формата.
А как насчет NSDecimalNumber
?
Хорошо заметный, если вы попытаетесь напечатать NSDecimalNumber
, который является подклассом NSNumber
, вышеприведенный toPrintfArg
распакует его как double
, и вы должны использовать формат с плавающей точкой, а не %@
. Обработка этого оставлена как упражнение.