Search code examples
objective-cswiftvariadic-functionsvariadic

Swift ambiguous methods with variadic parameter


I have the following method

static func t(key: String, params: AnyObject...) -> String{
    let string = .......
    if (params.count == 0){
        return string
    } else {
        return String(format: string, params)
    }
}

The problem is that i need to make this method available in Objective C which is impossible with variadic params. What I tried to do is create another method for objective c where params is an array. But then I also need to have third one for when there is no params.

@objc static func t(key: String, params: [AnyObject]) -> String
@objc static func t(key: String) -> String

But now swift complains that method t is ambiguous because 1st and 3rd method can take just the key.

How to make this work? I know I could use different names, but I would like to keep things consistent.

UPDATE: Another problem is that I can't call String(format:...) correctly once inside the function since params is an array and not a group of params. Any nice way to solve this?


Solution

  • You can do the following:

    @objc(C)
    public class C : NSObject {
    
        public static func t(key: String, params firstParam: AnyObject, _ params: AnyObject...) -> String {
            return t(key, params: [firstParam] + params)
        }
    
        @objc
        public static func t(key: String, params: [AnyObject]) -> String {
            return "TODO"
        }
    
        @objc
        static func t(key: String) -> String {
            return t(key, params: [])
        }
    }
    

    ... calling from Swift:

    C.t("")
    C.t("", params: 1)
    C.t("", params: 1, 2)
    C.t("", params: [1, 2, 3])
    

    This is bridged as follows:

    SWIFT_CLASS_NAMED("C")
    @interface C : NSObject
    + (NSString * _Nonnull)t:(NSString * _Nonnull)key params:(NSArray * _Nonnull)params;
    + (NSString * _Nonnull)t:(NSString * _Nonnull)key;
    - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
    @end
    

    And can be messaged as follows:

    #import "DarkSide.h"
    #import "ObjCInterOp-Swift.h"
    
    @implementation DarkSide
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            [C t:@"" params:@[]];
            [C t:@""];
        }
        return self;
    }
    @end
    

    Regarding the update to the question, there is a version of the formatted String initialiser that takes [CVarArgType] rather than CVarArgType...:

    func f(format format: String, _ params: [CVarArgType]) -> String {
        return String(format: format, arguments: params)
    }
    
    f(format: "%d %d %d", [1, 2, 3]) // "1 2 3"
    

    Only CVarArgType cannot be represented in Objective C, which really complicates things. In fact, if you really must use String.init(format: String, arguments: [CVarArgType]) and get to it from Objective C then I don't see at the moment how to avoid casting from AnyObjects to CVarArgTypes along the lines of the following murky code:

    @objc(C)
    public class C : NSObject {
    
        public static func t(key: String, params: CVarArgType...) -> String {
            return t(key, params: params)
        }
    
        public static func t(key: String, params: [CVarArgType]) -> String {
            return String(format: key, arguments: params)
        }
    
        @objc(t:params:)
        public static func t_objc(key: String, params: [AnyObject]) -> String {
            let args = params.map{
                // here you'll probably need to parse `key` so as to 
                // know what to cast into!
                ($0 as! NSNumber).integerValue as CVarArgType
            }
            return t(key, params: args)
        }
    
        @objc(t:)
        static func t_objc(key: String) -> String {
            return t(key)
        }
    }
    

    My suggestion is to either wean off things like String.init(format: String, arguments: [CVarArgType]), or to implement the base method in Objective C...