从@MartinR 评论和对NSLog 的引用不可用,您知道 Swift 可以调用 (Objective-)C 函数和方法,这些函数和方法va_list通过使用 Swift 类型CVarArg和CVaListPointer.
许多常见的(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,它是“免费桥接”到核心基础类型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对象解包为原始值类型,同时保留所有其他对象的格式%@(如示例中的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作为值传递的整数类型int,float值作为double)而在 C 中更改?答案可能是,但值所需的类型是提升的类型,因此在这种情况下CVarArg它不应该有任何区别——解包值的类型与预期的格式说明符相匹配。
怎么样NSDecimalNumber?
很好发现,如果你尝试打印一个NSDecimalNumber,它是 的一个子类NSNumber,上面toPrintfArg会将它解包为 adouble并且你必须使用浮点格式而不是%@。处理这个问题留作练习。