'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
}
}
Solution 1:[1]
Edit: I'm editing this question, because I think it might be 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 is 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 Int
s, and you can concatenate String
s, but you can't multiply String
s or concatenate Int
s. 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 pretty 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 switch
es will consume your entire codebase. This is why I recommend the protocol-base approach above.
Solution 2:[2]
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
Solution 3:[3]
I'd suggest using a tuple with optional values then create code to unwrap them accordingly.
The type Any
should be used sparingly, the fact that you know it's either a String
or Int
means the tuple could be the most appropriate solution in your use case.
func someFuction(type: String) -> (String?, Int?) {
//Do stuff here
}
Unwrap optional switch example:
let sometuple: (string: String?, int: Int?) = ("Hi", 10)
switch sometuple {
case let (.some(s), .some(i)):
print("String: \(s), Int: \(i)")
case let (.some(s), nil):
print(s)
case let (nil, .some(i)):
print(i)
case (nil, nil):
print("Nothing")
}
//prints "String: Hi, Int: 10"
The reason this works is because Optional
is an enum:
enum Optional<T> {
case some(x:T)
case none
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 |