Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift optional method arguments but at least one needs to be populated

I am creating a method for a class in swift with 2 arguments, and they are both optional. however, I need at least one of them to be populated for the method to successfully work, it doesn't matter which one

func someMethod(arg1: Sometype?, arg2: Sometype?)-> Void {
        //I need at least one argument to 
        //be populated to do what i need
}

In Objective-c we could throw an Assert if both of these object was nil. in Swift i"m wondering if there is a better way to do this than an assert.

like image 403
bolnad Avatar asked Apr 07 '15 15:04

bolnad


2 Answers

I agree with Airspeed Velocity that you should use overloads here, but I'd make them differently. Get rid of the optionals entirely.

func someMethod(#arg1: Sometype)-> Void {}

func someMethod(#arg2: Sometype)-> Void {}

func someMethod(#arg1: Sometype, #arg2: Sometype) -> Void {}

At which point, it should be obvious that these are really different methods:

func someMethodWithArg1(arg1: Sometype)-> Void {}

func someMethodWithArg2(arg2: Sometype)-> Void {}

func someMethod(#arg1: Sometype, #arg2: Sometype) -> Void {}

To make this concrete, consider if we were making a FixedLengthString class that you could pass a length to, or an existing string, or you could pass both and it'd repeat the string until it filled the length.

What you're describing would be:

func makeString(length: Int?, string: String?) -> FixedString

But rather than that, just make the methods:

func makeStringWithLength(length: Int) -> FixedString
func makeStringFromString(string: String) -> FixedString
func makeStringByFillingWith(string: String, totalLength: Int) -> FixedString

This makes it clearer how everything works, and you can't call it incorrectly. This is how you should be doing it in ObjC, too.

like image 164
Rob Napier Avatar answered Nov 17 '22 00:11

Rob Napier


You could use overloading instead:

// arg1 cannot be nil
func someMethod(arg1: Int, arg2: Int?)-> Void {
    println("arg2 was nil")
    somePrivateMethod(arg1,arg2)
}

// arg2 cannot be nil
func someMethod(arg1: Int?, arg2: Int)-> Void {
    println("arg1 was nil")
    somePrivateMethod(arg1,arg2)
}

// this version is needed to avoid annoying "ambiguous call"
// errors when passing two non-optionals in...
func someMethod(arg1: Int, arg2: Int)-> Void {
    println("neither was nil")
    somePrivateMethod(arg1,arg2)
}

private func somePrivateMethod(arg1: Int?, arg2: Int?)-> Void {
    // private method in which at least arg1 or arg2 will be guaranteed non-nil
}


someMethod(1, nil)  // prints "arg2 was nil"
someMethod(nil, 1)  // prints "arg1 was nil"
someMethod(1, 1)    // prints "neither was nil"

// but if you try this:
someMethod(nil, nil)  // error: cannot find an overload for 'someMethod' 
                      // that accepts an argument list of type '(nil, nil)'

The downside of this is that the caller is forced to unwrap the arguments – they just can't pass two optionals in without checking at lest one of them is non-nil. But this is a feature, not a bug! As it means it is physically impossible for the caller to accidentally call the API "the wrong way" and generate an assertion.

Of course, this does lead to the question of whether really you want multiple overloads that take different numbers of arguments or different types (see Rob's answer), which is also worth considering in the context of your use case.

like image 2
Airspeed Velocity Avatar answered Nov 16 '22 23:11

Airspeed Velocity