Swift function returning two different types

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


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.