How do you call an Objective-C variadic method from Swift?
Asked Answered
S

3

41

Supposing I have a class in Objective-c with a static method like this:

+ (NSError *)executeUpdateQuery:(NSString *)query, ...;

How do I call that from Swift? The autocomplete doesn't recognise it, and the compiler is unhappy with:

MyClassName.executeUpdateQuery("")

Complaining that 'MyClassName.Type does not have a member named executeUpdateQuery'

Sotos answered 13/6, 2014 at 0:4 Comment(0)
V
51

Write a va_list version of your variadic method;

+ (NSError *)executeUpdateQuery:(NSString *)query, ...
{
    va_list argp;
    va_start(argp, query);
    NSError *error = [MyClassName executeUpdateQuery: query args:argp];
    va_end(argp);
    
    return error;
}

+ (NSError *)executeUpdateQuery:(NSString *)query args:(va_list)args
{
    NSLogv(query,args);
    return nil;
}

This can then be called from Swift

MyClassName.executeUpdateQuery("query %d, %d %d", args: getVaList([1,2,3,4]))

Add an extension to support native Swift variadic args:

protocol CFormatFunction {
    class func executeUpdateQuery(_ format: String, _ args: CVarArg...) -> NSError?
}

extension MyClassName : CFormatFunction {
    class func executeUpdateQuery(_ format: String, _ args: CVarArg...) -> NSError?
    {
        return withVaList(args) { MyClassName.executeUpdateQuery(format, args: $0) }
    }
}

MyClassName.executeUpdateQuery("query %d %@ %.2f", 99, "Hello", 3.145)

Be careful, Swift doesn't provide NS_FORMAT_FUNCTION warnings (-Wformat)

MyClassName.executeUpdateQuery("query %@", 99)
Vendetta answered 13/6, 2014 at 0:39 Comment(3)
I'm trying to use the Extension code you have provided but I'm getting a "Use of undeclared type 'CVarArg'" error. How can I solve this?Siphon
Replacing CVarArg with CVarArgType solved the problem. Also - why do I need the protocol?Siphon
The docs suggest to use withVaList instead, somewhat ominously stating that getVaList is "best avoided." (swiftdoc.org/v2.1/func/getVaList/#func-getvalist_-cvarargtype)Jumpoff
J
8

CVArgType is useful in presenting C "varargs" APIs natively in Swift. (Swift Docs)

If you have

+ (int)f1:(int)n, ...;

you first need to make a va_list version:

+ (int)f2:(int)n withArguments:(va_list)arguments

This can be done without duplicating code by calling the va_list version from the variadic version. If you didn't write the original variadic function it may not be possible (explained in this reference).

Once you have this method, you can write this Swift wrapper:

func swiftF1(x: Int, _ arguments: CVarArgType...) -> Int {
     return withVaList(arguments) { YourClassName.f2(x, withArguments :$0) }
}

Note the omitted external parameter name (_ before arguments), which makes the call syntax for swiftF1 just like a normal C variadic function:

swiftF1(2, some, "other", arguments)

Note also that this example doesn't use getVaList because the docs say it is "best avoided."

You can further put this function in a Swift extension of the original class, if you want.

Jumpoff answered 19/1, 2016 at 8:45 Comment(0)
U
1

In Objective C

MyClassName.h

+ (BOOL)executeSQL:(NSString *)sql args:(va_list)args;

MyClassName.m

+ (BOOL)executeSQL:(NSString *)sql args:(va_list)arguments
{
    NSLogv(sql, arguments);

    sql = [[NSString alloc] initWithFormat:sql arguments:arguments];
    va_end(arguments);
}

Swift - add in its class Works perfect

protocol CFormatFunction {
   class func executeSQLArg(format: String, _ args: CVarArgType...) -> Bool
}

extension MyClassName : CFormatFunction {
   class func executeSQLArg(format: String, _ args: CVarArgType...) -> Bool
   {
        return MyClassName(format, args:getVaList(args))
   }
 }

How to use

Swift

MyClassName.executeSQLArg(query, "one","two",3)

Objetive C

[MyClassName executeSQLArg:query, @"one",@"two",@3]
Uproarious answered 14/1, 2015 at 13:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.