如何将Objective-C语言(`va_list`,`va_start`,`va_end`)转换为快速语言?

时间:2018-11-09 06:09:58

标签: objective-c swift

这是我的密码。它们是Objective-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);
}

如何将Objective-C语言(va_listva_startva_end)转换为快速语言?

我还需要在Objective-C xxx.m文件中调用此swift方法。

需要帮助。谢谢!

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

更新:

我尝试了MartinR的答案NSLog is unavailable,但是出了点问题,我无法在方法前添加@objc,需要帮助。

my codes...

1 个答案:

答案 0 :(得分:0)

在@MartinR的注释和对NSLog is unavailable的引用中,您知道Swift可以通过使用Swift类型va_list来调用采用CVarArg参数的(Objective-)C函数和方法。 CVaListPointer

许多常见的(Objective-C)可变参数函数和方法都有一个va_list的同级元素,因此Swift中的这种支持提供了对它们的访问。

  

我还需要在Objective-C xxx.m文件中调用此swift方法。

但是,如果您希望采用另一种方法,那么在编写了Objective-C方法的Swift可变参数函数版本后,您发现无法调用它。您试图问解How do you call a Swift variadic method from 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库类型,而您不需要的其他类型(Apple's CVarArg documentation中列出了3287)。这里的私有核心功能微不足道,您可能希望做一些更多的事情。

现在,您希望从Objective-C调用Log(),但是,正如您所发现的,由于CVarArg,您不能。但是,Objective-C可以调用带有NSObject参数的Swift函数,并且NSObject实现CVarArg,这使我们第一次尝试:

   // Objective-C entry point
   @objc static func Log(_ format : String, args : [NSObject])
   {
      withVaList(args) { LogV(format, $0) }
   }

这按原样工作,但是每个参数都必须是 object 并使用%@进行格式化,切换到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) }
   }

暂时在Objective-C中跳过toPrintfArg(它又大又丑),我们可以将此版本称为:

[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会做什么?

在上面,我们必须将 objects 的数组传递给Swift,然后将所有原始值包装为NSNumber对象。

在Objective-C中,一个NSNumber对象并没有透露太多有关其包装内容的信息,访问方法(.doubleValue.integerValue等)将转换将所有包装的值放入所请求类型的值中,然后将其返回。

然而,NSNumber与Core Foundation类型CFBooleanCFNumber是“免费桥接”的;前者用于布尔值(很明显!),而后者用于所有其他数字类型,并且与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对象恢复为原始值类型,而所有其他对象都将保留为{{1} }(如示例中的%@NSString对象所示。)

希望有所帮助,至少要使它感到困惑!好奇/专家注意事项如下。


注释和警告

保留C指针

在上面的示例中,通过将C字符串NSDate转换为"hello"来传递,C字符串类型的大小与指针相同,而不是指针值。在这种情况下就可以了,intptr_t本质上是一个 untyped 字节的鲍勃,格式告诉va_list接下来的字节解释为哪种类型,转换为{{1 }}保持相同的字节/位,并允许将指针包装为NSLogv()

但是,如果您的应用程序需要在Swift端具有实际的指针,则可以将C字符串包装为intptr_t

NSNumber

并通过添加以下内容将其解包到NSValue中:

[NSValue valueWithPointer:"hello"]

这将产生类型为toPrintfArg的值,该值实现 if let ptr = (item as? NSValue)?.pointerValue { return ptr.bindMemory(to: Int8.self, capacity: 1) } (与UnsafeMutablePointer<Int8>无关)。

您是否总是返回相同的类型?

如果将C类型包装为CVarArg,然后如上所述将其拆开,由于 argument升级(这意味着小于{{1} }在C中以capacity值传递,NSNumber值以int传递?答案是也许,但是int值的必需类型是提升的类型,因此在这种情况下 不会有任何区别–未包装的类型符合预期格式说明符的值。

float怎么样?

发现得很好,如果您尝试打印double的子类CVarArg,则上述NSDecimalNumber会将其解压缩为NSDecimalNumber,您必须使用浮点格式,而不是NSNumber。处理此问题留作练习。