0

这是我的代码。它们是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_list,,, va_start)va_end转成swift语言?</p>

我还需要在objective-c xxx.m 文件中调用这个快速方法。

需要帮忙。谢谢!

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

更新:

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

我的代码...

4

1 回答 1

2

从@MartinR 评论和对NSLog 的引用不可用,您知道 Swift 可以调用 (Objective-)C 函数和方法,这些函数和方法va_list通过使用 Swift 类型CVarArgCVaListPointer.

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

我还需要在objective-c xxx.m 文件中调用这个快速方法。

但是,您希望另辟蹊径,并且在编写了 Objective-C 方法的 Swift 可变参数函数版本后,您发现无法调用它。您试图询问解决方案是什么,您如何从 Objective-C 调用 Swift 可变参数方法?,该问题的间接答案(您的问题被标记为重复)提供了一个提示——使用数组——但不能处理格式化打印类型场景所需的一般性。让我们看看我们能不能到达那里......

(使用 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 的CVarArg文档中列出了 3287 个)。这里的私有核心功能是微不足道的,您可能希望做一些更复杂的事情。

现在您希望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什么呢?

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

在 Objective-C 中,一个NSNumber对象并没有透露太多关于它包装的内容,访问方法(.doubleValue.integerValue)会将包装的任何值转换为请求类型的值并返回它

然而NSNumber,它是“免费桥接”到核心基础类型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对象解包为原始值类型,同时保留所有其他对象的格式%@(如示例中的NSStringandNSDate对象所示)。

希望有帮助,至少比它更令人困惑!好奇/专家的注意事项如下。


注意事项和注意事项

保留 C 指针

在上面的示例中,C 字符串"hello"是通过将其转换intptr_t为 C 整数类型来传递的,它是与指针大小相同的 C 整数类型,而不是指针值。在这种情况下,这很好,ava_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)
      }

这会产生一个 type 的值UnsafeMutablePointer<Int8>,它实现CVarArg(而后者capacity是无关紧要的)。

你总是得到相同的类型吗?

如果您将 C 类型包装为 anNSNumber然后像上面那样将其解包,则类型是否会由于 C 中的参数提升(这意味着小于int作为值传递的整数类型intfloat值作为double)而在 C 中更改?答案可能是,但值所需的类型是提升的类型,因此在这种情况下CVarArg它不应该有任何区别——解包值的类型与预期的格式说明符相匹配。

怎么样NSDecimalNumber

很好发现,如果你尝试打印一个NSDecimalNumber,它是 的一个子类NSNumber,上面toPrintfArg会将它解包为 adouble并且你必须使用浮点格式而不是%@。处理这个问题留作练习。

于 2018-11-11T10:37:46.427 回答