这是我的密码。它们是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
)转换为快速语言?
我还需要在Objective-C xxx.m文件中调用此swift方法。
需要帮助。谢谢!
================================================ ======================
更新:
我尝试了MartinR的答案NSLog is unavailable,但是出了点问题,我无法在方法前添加@objc,需要帮助。
答案 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类型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
对象恢复为原始值类型,而所有其他对象都将保留为{{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
。处理此问题留作练习。