avoid implicit enum raw value conversion - swift

I am attempting to avoid the implicit conversion of an enum into a string in a case like this:
enum Animal {
case cat
}
func getTag(forAnimal animal: Animal) -> String {
// fails with "Cannot convert value of type 'Animal' to specified type 'String' which is good!!!"
// return animal
// does return "cat" which I don't want developers to be able to do!!!
return "\(animal)"
}
print(getTag(forAnimal: .cat)) // prints "cat"
The 2nd return works because print calls .description on the object.
Swift apparently adopts CustomStringConvertible for enums automatically and returns the case name as a String. Edit: Apparently , print does not call .description.
I thought about conforming to the protocol and alert developers (with something that throws an error perhaps).
But what I am wanting to do really is to have the enum NOT conform to said protocol, or have a compile-time error. Is this possible?

You can extend StringInterpolation and implement an appendInterpolation method for that specific type. This way you can simply determine how your enumeration would interpolate its value. If you don't want any string to be generated you just need to create a dummy method:
enum Animal {
case cat
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Animal) { }
}
Testing:
"\(Animal.cat)" // "" it results in an empty string
If you want a compile-time error you can add a deprecated availability warning:
#available(swift, deprecated: 2)
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Animal) { }
}

Related

Swift. Can't use Type variable as specification to generic method

I try to make a generic method in Swift without an object of a generic type with this code:
protocol TestProtocol {}
class TestClass1: TestProtocol {}
class TestClass2: TestProtocol {}
class TestClass3: TestProtocol {}
class TestClass4: TestProtocol {}
enum TestEnum {
case one, two, three, four
var type: TestProtocol.Type {
switch self {
case .one: return TestClass1.self
case .two: return TestClass2.self
case .three: return TestClass3.self
case .four: return TestClass4.self
}
}
}
class TestMethods {
func prepareToGet(kind: TestEnum) {
getTest(as: kind.type)
}
func getTest<TestType: TestProtocol>(as: TestType.Type) {
// here I need specified type of TestProtocol to handle it
}
}
But with Type variable. But I have errors:
Cannot convert value of type 'TestProtocol.Type' to expected argument type 'TestType.Type'
Generic parameter 'TestType' could not be inferred
I feel blind, but I really don't see the problem.
Use TestProtocol.Type instead of TestType.Type, i.e.
func getTest(as type: TestProtocol.Type) {}
My understanding is that you want the function to know the class of the generic by using information from the enum, but generic functions need to be able to infer the type at compile time, and the result of TestEnum.type is only available at run time.
I'm not sure what the real case scenario you have, but I think that the best you can do is to have a function that returns TestProtocol, either as a type or object, whatever you need, and conditionally cast it to the type you want.
If you give us a more real-life example, we might be able to help you find a better solution.

Implement generic protocol method with but use generic for whole class

I'm am trying to implement a protocol method that has a generic argument, but then use the generic type for my entire class instead of just on the method, something like this
protocol FirstProtocol {
}
protocol SecondProtocol {
func foo<T: FirstProtocol>(argument: T)
}
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<T>(argument: T) {
value = argument // ERROR: Cannot assign value of type 'T' to type 'T?'
}
}
So the swift compiler accepts that foo<T>(argument:T) matches the method of SecondProtocol, if I comment out the error line it compiles fine, but it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
The type of argument and value are indeed different types. The T generic parameter in foo is just an identifier, and I can change it to anything else:
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<AnythingElse>(argument: AnythingElse) {
// MyType still conforms to SecondProtocol
}
}
The T in foo is a brand new generic parameter, different from the T in MyType. They just so happens to have the same name.
Note that when you declare a generic method, it's the caller that decides what the generic type is, not the generic method. What foo is trying to say here is "I want the T in foo to be the same type as the T in MyType", but it can't say that about its own generic parameters!
One way to fix it is to make SecondProtocol have an associated type:
protocol SecondProtocol {
// name this properly!
associatedtype SomeType: FirstProtocol
func foo(argument: SomeType)
}
class MyType<T: FirstProtocol>: SecondProtocol {
typealias SomeType = T // here is where it says "I want 'SomeType' to be the same type as 'T'!"
var value: T? = nil
func foo(argument: T) {
value = argument
}
}
it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
think about this case:
class A: FirstProtocol {
}
class B: FirstProtocol {
}
class A and B is the acceptable generic type for func foo(argument: T){}, but can you assign an instance of class A to class B?
class MyType<T: FirstProtocol>: SecondProtocol
remove ": FirstProtocol"should work, or use a base class to replace FirstProtocol

How to use generic default parameters

This is my code:
class Person {
init<T: RawRepresentable>(raw: T = Child.johnDoe) {}
}
enum Child: String {
case johnDoe
}
It doesn't compile. The error is:
Default argument value of type 'Child' cannot be converted to type 'T'
Why can't it be converted? According to the docs, Child.someEnum is RawRepresentable:
Enumerations with Raw Values For any enumeration with a string,
integer, or floating-point raw type, the Swift compiler automatically
adds RawRepresentable conformance. When defining your own custom
enumeration, you give it a raw type by specifying the raw type as the
first item in the enumeration’s type inheritance list.
This also compiles:
class Person {
static func accept<T: RawRepresentable>(raw: T) where T.RawValue == String {}
}
enum Child: String {
case johnDoe
}
Person.accept(raw: Child.johnDoe)
Why doesn't it work as a default parameter?
Use case: I want to accept any RawPresentable value, so I can extract the rawValue from it. I want to provide a default value (always "") (I just create a struct with rawValue = ""). I do not want to create multiple initializers, since I got some subclasses and that would get a mess. The best for me is just to provide a default RawRepresentable object.
When I add a cast:
init(ty: T = (Child.johnDoe as! T)) where T.RawValue == String {
}
Or make it nillable:
(ty: T? = nil)
It compiles. But now I can not call:
let x = Person()
It gives the error:
Generic parameter 'T' could not be inferred
This is certainly possible. However, you have to use your own protocol and add the default value to that protocol:
protocol MyRawRepresentable: RawRepresentable {
static var defaultValue: Self { get }
}
class Person {
init<T: MyRawRepresentable>(raw: T = T.defaultValue) {}
}
enum Child: String, MyRawRepresentable {
case johnDoe
static let defaultValue: Child = .johnDoe
}
There is another issue though. How will you specify the generic type if you use the default parameter value and all you will have will be just Person.init()?
The only solution I see is to also specify a default generic type which means you actually want:
class Person {
init<T: RawRepresentable>(raw: T) {
}
convenience init() {
self.init(raw: Child.johnDoe)
}
}
Unless you actually want to make Person itself a generic class because then you could just use
Person<Child>.init()

How can I create a function in Swift that returns a Type which conforms to a protocol?

How can I create a function in Swift that returns a Type which conforms to a protocol?
Here is what I'm trying right now, but it obviously won't compile like this.
struct RoutingAction {
enum RoutingActionType{
case unknown(info: String)
case requestJoinGame(gameName: String)
case requestCreateGame(gameName: String)
case responseJoinGame
case responseCreateGame
}
// Any.Type is the type I want to return, but I want to specify that it will conform to MyProtocol
func targetType() throws -> Any.Type:MyProtocol {
switch self.actionType {
case .responseCreateGame:
return ResponseCreateGame.self
case .responseJoinGame:
return ResponseJoinGame.self
default:
throw RoutingError.unhandledRoutingAction(routingActionName:String(describing: self))
}
}
}
I would personally prefer returning an instance instead of a type but you can do it that way too. Here's one way to achieve it:
protocol MyProtocol:class
{
init()
}
class ResponseCreateGame:MyProtocol
{
required init() {}
}
class ResponseJoinGame:MyProtocol
{
required init() {}
}
enum RoutingActionType
{
case unknown(info: String),
requestJoinGame(gameName: String),
requestCreateGame(gameName: String),
responseJoinGame,
responseCreateGame
// Any.Type is the type I want to return, but I want to specify that it will conform to MyProtocol
var targetType : MyProtocol.Type
{
switch self
{
case .responseCreateGame:
return ResponseCreateGame.self as MyProtocol.Type
case .responseJoinGame:
return ResponseJoinGame.self as MyProtocol.Type
default:
return ResponseJoinGame.self as MyProtocol.Type
}
}
}
let join = RoutingActionType.responseJoinGame
let objectType = join.targetType
let object = objectType.init()
Note that your protocol will need to impose a required init() to allow creation of instances using the returned type.
Note2: I changed the structure a little to make my test easier but i'm sure you'll be able to adapt this sample to your needs.
Why do you not want to use simple:
func targetType() throws -> MyProtocol
?
EDIT:
I think you can't. Because if you return Type actually you return an instance of class Class and it can't conform your protocol. This runtime's feature was inherited from objective-c. You can see SwiftObject class.

Swift, classes based on extended protocol don't conform to original protocol

These protocols are giving me nightmares.
I am trying to implement a couple protocols and classes conforming to them, such that I can have default implementations, but customized implementations are available by extending the protocols/classes. So far, this is what I have:
protocol ProtA {
var aProperty: String { get set }
var anotherProperty:String { get set }
func aFunc (anArgument: String) -> String
}
protocol ProtB: ProtA {
var aThirdProperty: String { get set }
}
protocol ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA)
}
class ClassA: ProtA {
var aProperty: String = "Hello, I'm a String."
var anotherProperty: String = "I'm not the same String."
func aFunc (anArgument: String) -> String {
return anArgument
}
}
class ClassB: ProtB {
var aProperty: String = "Hello, I'm a String."
var anotherProperty: String = "I'm not the same String."
var aThirdProperty: String = "I'm yet another String!"
func aFunc (anArgument: String) -> String {
return anArgument
}
}
class ClassC: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA) {
print (anotherParameter.aProperty) // Works fine.
}
}
Then, if I do
class ClassC: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA) {
print (anotherParameter.aProperty) // Works fine.
}
}
But, if I do
class ClassD: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtA) {
print (anotherParameter.aThirdProperty) // Value of type 'ProtA' has no member 'aThirdProperty'
}
}
and, if instead I do
class ClassE: ProtC {
func doSomething(parameter: Int, with anotherParameter: ProtB) {
print (anotherParameter.aThirdProperty) // Type 'ClassE' does not conform to protocol 'ProtC'
}
}
What am I doing wrong?
The problem
When inheriting from a type, you cannot narrow down the types of the parameters used in overridden functions. This is what you've done by changing the parameter from type ProtA (a more general type), to ProtB (a more specific type).
This is a consequence of the Liskov substitution principle. Simply put, a subclass must be able to do (at minimum) everything that the superclass can do.
ProtC establishes that all conforming types have a function func doSomething(parameter: Int, with anotherParameter: ProtA), with type (Int, ProtA) -> Void).
Your modified function in ClassE has type (Int, ProtB) -> Void. However, this function can no longer act as a substitute for the one it overrides.
Suppose that it was possible to do what you tried. Watch what would happen:
let instanceConformingToProtA: ProtA = ClassA()
let instanceConformingToProtC: ProtC = ClassE()
// This would have to be possible:
instanceConformingToProtC(parameter: 0, amotherParameter: instanceConformingToProtA)
But, ClassE() can't take instanceConformingToProtA as a valid argument to its second parameter, because it's a ProtA, not the required ProtB.
The solution
The solution to this problem is entirely dependant on what you're trying to achieve. I would need further information before being able to proceed.
As a rule of thumb, when overriding inherited members:
Parameter types must be the same, or more general.
E.g. you can't override a function with a parameter of type Car, and change the parameter type to RaceCar. Doing so breaks your classes ability to work with RaceCars, which it must be able to do by the LSP.
E.g. you can override a function with a parameter of type Car, and change the parameter to Vehicle. Doing so preserves your classes' ability to work with `Vehicles.
Return types must be the same, or more specific.
E.g. you can't override a function with a return type of Car with a function that returns Vehicle. Doing so would mean the returned value is "less powerful" than the super class guarantees it should be.
E.g. you can override a function with a return type of Car with a function that returns RaceCar. Doing so would mean the returned value is "more powerful", and it does at least as much as what the super class guarentees.
There's nothing wrong. You should just make sure that the declarations are semantically consistent.
You should either create ProtD declaring the method with a ProtB parameter OR unwrapping the gotten ParamA parameter to use it as ProtB.
func doSomething(parameter: Int, with anotherParameter: ProtB) {
if let a = anotherParameter as? ProtA {
print (a.aThirdProperty)
}
}