как перевести язык Objective-C (`va_list`,` va_start`, `va_end`) в язык swift? - PullRequest
0 голосов
/ 09 ноября 2018

Это мои коды. они объективно-c языком:

- (void)objcMethod:(NSString *)format, ...
{
va_list args;

va_start(args, format);

NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
NSLog(@"%@", msg);

va_end(args);

va_start(args, format);

va_end(args);
}

как перевести язык target-c (va_list, va_start, va_end) на быстрый язык ift

Мне также нужно вызвать этот быстрый метод в файле target-c xxx.m.

Нужна помощь. спасибо!

=============================================== =======================

обновление:

Я попробовал ответ MartinR NSLog недоступен , но что-то не так, я не могу добавить @objc перед методом, нужна help.thanks.

мои коды ...

1 Ответ

0 голосов
/ 11 ноября 2018

Из комментария @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, и вы должны использовать формат с плавающей точкой, а не %@. Обработка этого оставлена ​​как упражнение.

...