Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift function returning two different types

I need a function that can return either a String or an Int depending on the parameters entered eg:

func getValue (type: String) -> (String || Int) {  //this line is obviously wrong
    if type == "type1" {
        return "exampleString"
    }
    else if type == "type2"
        return 56
    }
}
like image 763
Matt Wyeth Avatar asked Oct 14 '16 02:10

Matt Wyeth


People also ask

Can a function have multiple return types?

Use of returnA function can not return multiple values, but similar results can be obtained by returning an array.

Can we return two values from a function in Swift?

We can return multiple values from a swift function. It can be achieved by returning a tuple. Using a tuple, we can group multiple values of different types and we can return that. Each element can be accessed by its name.

How can I return multiple values from a function in Swift?

Swift's functions have a single return type, such as Int or String , but that doesn't mean we can only return a single value. In fact, there are two ways we can send back multiple pieces of data: Using a tuple, such as (name: String, age: Int) Using some sort of collection, such as an array or a dictionary.

How does a function return a value in Swift?

If you want to return your own value from a function, you need to do two things: Write an arrow then a data type before your function's opening brace, which tells Swift what kind of data will get sent back. Use the return keyword to send back your data.


2 Answers

Edit: I'm editing this answer, because I think my old answer is mostly just leading people down the wrong road.

The ability to return one of two (completely-unrelated) types might sound like it would solve your problem, but it almost certainly does not. Swift is a statically typed language, which means that it limits what you can do to a value, depending on its type (this limitation is beneficial, because you know this will always work!).

You can multiply Ints, and you can concatenate Strings, but you can't multiply Strings or concatenate Ints. But what if you had a value that was either a hypothetical (Int || String)? What could you do with that?

  • Well you can't multiply, because what if it was a String underneath? That wouldn't make sense.

  • You couldn't concatenate it either, because what if the underlying value was an Int? That wouldn't make sense, either.

The only thing that would be allowable, is to only do those things which are supported by both Int or String. And what is that exactly? Well, you could get the description: String, and that's... about it.

The modern solution to this problem is to describe the capabilities of the result using a protocol, and to return that. Here's an example:

protocol ModeOfTransport {
    func transport(cargo: String)
}

struct Car: ModeOfTransport {
    func transport(cargo _: String) {
        print("I'll put the light cargo in my trunk and drive it.")
    }
}

struct Train: ModeOfTransport {
    func transport(cargo: String) {
        print("I'll put attach the heavy cargo in a new car and pull it.")
    }
}

func getAppropriateModeOfTransport(cargoWeight: Int) -> ModeOfTransport {
    if cargoWeight < 500 {
        return Car()
    } else {
        return Train()
    }
}

let modeOfTransport = getAppropriateModeOfTransport(cargoWeight: 1234)
modeOfTransport.transport(cargo: "Sample Cargo")

Original answer:

You can use Enumeration

You can use an enumeration with associated values to achieve the behaviour you're looking for. They're much like a nicer version of C's unions.

enum Foo { //TODO: Give me an appropriate name.
    case type1(String)
    case type2(Int)
    
    static func getValue(type: String) -> Foo {
        switch (type) {
            case "type1": return type1("exampleString")
            case "type2": return type2(56)
            default: fatalError("Invalid \"type\"");
        }
    }
}

let x = Foo.getValue(type: "type1")

This is actually very annoying, because the only way to do anything sensible with these values it to consume them conditionally, by switching on its type and responding accordingly:

switch x {
    case .type1(let string): funcThatExpectsString(string)
    case .type2(let int): funcThatExpectsInt(int)
}

If you're not careful, these switches will consume your entire codebase. This is why I recommend the protocol-base approach above.

like image 85
Alexander Avatar answered Oct 13 '22 00:10

Alexander


I faced a similar problem and I solved in this way (you can use default associated value introduced in Swift 5.1 and opaque return type)

class PersistanceHelper {

    enum PersistenceType {
        case userStatus(status: String = "")
        case firstAccess(isFirstAccess: Bool = true)
        case biometricsEnabled(isBiometricsEnabled: Bool = true)
        case notificationToken(token: String = "")

        func getKey() -> String {
            switch self {
            case .userStatus        : return "userStatusKey"
            case .firstAccess.      : return "firstAccessKey"
            case .biometricsEnabled : return "biometricsEnabledKey"
            case .notificationToken : return "notificationTokenKey"
            }
        }
    }

    static func save(_ objectType: PersistenceType) {
        switch objectType {
        case .userStatus(let payload), .notificationToken(let payload):
            UserDefaults.standard.set(payload, forKey: objectType.getKey())
        case .firstAccess(let payload), .biometricsEnabled(isBiometricsEnabled: let payload):
            UserDefaults.standard.set(payload, forKey: objectType.getKey())
        }
    }

    static func load<T>(_ objectType: PersistenceType) -> T? {
        UserDefaults.standard.object(forKey: objectType.getKey()) as? T
    }

}

And then use it where you need...

PersistanceHelper.save(.notificationToken(token: "93028184-87be-4a62-bcc9-70ec08d6fe7e"))
PersistanceHelper.save(.biometricsEnabled(isBiometricsEnabled: true))

if let token: String = PersistanceHelper.load(.notificationToken()),
    let isBiometricEnabled: Bool = PersistanceHelper.load(.biometricsEnabled()) {
    print(token)
    print(isBiometricEnabled)
}

The enums with associated values allow to write a self explaining code... at least to me :D

like image 22
DungeonDev Avatar answered Oct 13 '22 00:10

DungeonDev