I am not very good with protocols, and I'm quite stuck right now. I am trying to make a protocol for an enum, which has a name property (or function if it needs to be).
This will allow me to get the name of an enum value, similar to conforming to String alone, but with the first letter capitalised instead. For example, what I want to happen:
enum Furniture: Named {
case table, chair, lamp
}
Furniture.table.name // Returns 'Table'
Furniture.chair.name // Returns 'Chair'
Furniture.lamp.name // Returns 'Lamp'
Here is what I have so far:
protocol Named {
var name: String { get }
}
extension Named where Named: String {
var name: String {
let str: String = self.rawValue
return str.prefix(1).uppercased() + str.dropFirst()
}
}
This gives me errors, and when I try to correct them, another error pops up.
Right now, this shows me the following errors:
Type 'Named' constrained to non-protocol, non-class type 'String'
Value of type 'Self' has no member 'rawValue'
How can I fix this?
Enums get their raw value via the RawRepresentable protocol, so that's what you should use when constraining your extension:
extension Named where Self: RawRepresentable, RawValue == String {
var name: String {
let str: String = self.rawValue
return str.prefix(1).uppercased() + str.dropFirst()
}
}
You also need to declare that your enum uses a String as the type of its rawValue:
enum Furniture: String, Named {
case table, chair, lamp
}
Set the Furniture enum to a string raw type, the Swift compiler automatically adds RawRepresentable conformance.
protocol Named {
var name: String { get }
}
enum Furniture: String, Named {
case table, chair, lamp
}
extension Named where Self: RawRepresentable, RawValue == String {
var name: String {
return rawValue.capitalized
}
}
A Unit Tests to validate the Furniture enum,
import XCTest
class FurnitureTests: XCTestCase {
func testEnumDisplayNameFirstLetterCapitlized() {
XCTAssertEqual(Furniture.table.name, "Table")
XCTAssertEqual(Furniture.chair.name, "Chair")
XCTAssertEqual(Furniture.lamp.name, "Lamp")
}
func testEnumRawValue() {
XCTAssertEqual(Furniture.table.rawValue, "table")
XCTAssertEqual(Furniture.chair.rawValue, "chair")
XCTAssertEqual(Furniture.lamp.rawValue, "lamp")
}
}
Related
I have a protocol and a class which I want to extend. The protocol requires field of some type and the class has a field with the same name and the type as Implicitly Unwrapped Optional of this type.
Can this class be extended by this protocol? If yes, then how?
If I try to write an extension, Xcode give an error of not conforming. But if I add the field into the extension, it gives an error of redeclaration.
protocol Named {
var name: String { get }
}
class Person {
var name: String!
}
extension Person: Named {
// Type 'Finances.Account' does not conform to protocol 'Named'
}
Property names and types declared in a protocol must exactly be matched by the conforming classes.
So you cannot resolve the error without changing the property type in either the protocol or the conforming type. You could also rename one of the properties and add the matching property to the conforming type as a new field.
So either do:
protocol Named {
var name: String { get }
}
class Person {
var name: String
init(_ name:String) {
self.name = name
}
}
extension Person: Named {
}
Or
protocol Named {
var name: String { get }
}
class Person {
var _name: String!
}
extension Person: Named {
var name: String {
return _name
}
}
As #user28434 pointed out, there's a(n ugly) workaround. You can create a wrapper protocol that matches the optionality of the Person class, make that protocol inherit from the original protocol, declare the non-optional variable in an extension on the new protocol and make Person conform to the new protocol instead of the original Named.
protocol Named {
var name: String { get }
}
class Person {
var name: String!
}
protocol Namedd: Named {
var name: String! { get }
}
extension Namedd {
var name: String {
return name!
}
}
extension Person: Namedd {
}
I have created the following protocol and enum conforming to the protocol as shown below:
protocol HamburgerOption {
static var all :[HamburgerOption] { get }
var title :String { get }
}
enum Meat :HamburgerOption {
private static var allCases :[Meat] = [.chicken, .beef]
case chicken
case beef
static var all :[HamburgerOption] {
return Meat.allCases
}
var title :String {
switch self {
case .chicken:
return "Chicken"
case .beef:
return "Beef"
}
}
}
When I create the following struct it gives me error:
// Value of type 'Meat.Type' does not conform to expected element type //'HamburgerOption'
struct HamburgerOptions {
var all :[HamburgerOption] {
return [Meat, Sauces]
}
}
Your property is defined to return an array of instances that conform to HamburgerOption. However, by returning [Meat, Sauces], you are returning an array of types rather than instances. What you need to do is to return an array of instances. You can do this simply by adding the results of the all methods for your types:
return Meat.all + Sauces.all
(For any Sheldon Cooper wannabes out there, yes, if he actually needed to make an array of types, he'd need to postpend each one with .self)
We're trying to create a function addQueryItem which ultimately uses a string and an optional string internally.
For more flexibility in the API, rather than use String for the argument types, we are instead using CustomStringConvertible (which String implements) so we can use anything that can be represented as a string.
Additionally, so we can pass it String-based enums, we also want it to accept RawRepresentable types where RawValue is a CustomStringConvertible itself.
However, since we're now technically accepting two different kinds of values for each parameter, we end up having to create a 'matrix of overloads'--four total--for each combination of the two types.
My first thought was to use protocol-oriented programming by extending RawRepresentable so it adheres to CustomStringConvertible if its RawValue was also a CustomStringConvertible. Then I could just pass that directly to the version which takes two CustomStringConvertible arguments and eliminate the other three. However, the compiler didn't like it because I'm trying to extend a protocol, not a concrete type.
// This doesn't work
extension RawRepresentable : CustomStringConvertible
where RawValue:CustomStringConvertible {
var description: String {
return self.rawValue
}
}
As a result of not being able to do the above, as mentioned, I have to have all four of the following:
func addQueryItem(name:CustomStringConvertible, value:CustomStringConvertible?){
if let valueAsString = value.flatMap({ String(describing:$0) }) {
queryItems.append(name: String(describing:name), value: valueAsString)
}
}
func addQueryItem<TName:RawRepresentable>(name:TName, value:CustomStringConvertible?)
where TName.RawValue:CustomStringConvertible {
addQueryItem(name: name.rawValue, value: value)
}
func addQueryItem<TValue:RawRepresentable>(name:CustomStringConvertible, value:TValue?)
where TValue.RawValue:CustomStringConvertible {
addQueryItem(name: name, value: value?.rawValue)
}
func addQueryItem<TName:RawRepresentable, TValue:RawRepresentable>(name:TName, value:TValue?)
where TName.RawValue:CustomStringConvertible,
TValue.RawValue:CustomStringConvertible
{
addQueryItem(name: name.rawValue, value: value?.rawValue)
}
So, since it doesn't look like it's possible to make RawRepresentable to adhere to CustomStringConvertible, is there any other way to solve this 'matrix-of-overloads' issue?
To expand on my comments, I believe you're fighting the Swift type system. In Swift you generally should not try to auto-convert types. Callers should explicitly conform their types when they want a feature. So to your example of an Order enum, I believe it should be implemented this way:
First, have a protocol for names and values:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
Now for string-convertible enums, it's nice to not have to implement this yourself.
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
But, for type-safety, you need to explicitly conform to the protocol. This way you don't collide with things that didn't mean to be used this way.
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
Now maybe QueryItems really has to take strings. OK.
class QueryItems {
func append(name: String, value: String) {}
}
But the thing that wraps this can be type-safe. That way Order.buy and Purchase.buy don't collide (because they can't both be passed):
class QueryBuilder<Name: QueryName, Value: QueryValue> {
var queryItems = QueryItems()
func addQueryItem(name: QueryName, value: QueryValue?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
You can use the above to make it less type-safe (using things like StringCustomConvertible and making QueryBuilder non-generic, which I do not recommend, but you can do it). But I would still strongly recommend that you have callers explicitly tag the types they plan to use this way by explicitly labelling (and nothing else) that they conform to the protocol.
To show what the less-safe version would look like:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
extension QueryName where Self: CustomStringConvertible {
var queryName: String { return self.description }
}
extension QueryValue where Self: CustomStringConvertible {
var queryValue: String { return self.description }
}
class QueryItems {
func append(name: String, value: String) {}
}
class QueryBuilder {
var queryItems = QueryItems()
func addQueryItem<Name: QueryName, Value: QueryValue>(name: Name, value: Value?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
No, you cannot conform a protocol to another protocol via an extension. The language does not support it.
I defined enums as confirming to a protocol Eventable:
protocol Eventable {
var name: String { get }
static var all: [Eventable] { get }
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
var name: String {
return self.rawValue
}
static var all: [Eventable] {
return [
MyEnum.bla,
MyEnum.blu
]
}
}
I have other enums like MyEnum also under the form:
enum Bla: String, Eventable {
}
I have two questions:
for the enums having a String data type, I would like to avoid duplicating the generation of the variable name:
var name: String
I am not sure how to write that in Swift. I tried to play around with the "where" clause but not success. How can I achieve that?
when I write my enums and conform to the protocol for that part:
static var all: [Eventable] { get }.
I would like that for the enum MyEnum, it constrains the variable to:
static var all: [MyEnum] { ... }
because for now I can put in the returned array any element being an Eventable and it's not what I need.
Amongst other things, I tried to define a generic constraint in the protocol for it, but I get the following error:
Protocol 'Eventable' can only be used as a generic constraint because
it has Self or associated type requirements
Thank you very much for the help!
For your second question, you just need to use Self:
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
Self, similar to self, just means "the current type".
The first question is a little bit harder because you can't really get all the values of an enum safely. See here for more info. The closest I got was:
extension Eventable where Self: RawRepresentable, Self.RawValue == String {
var name: String {
return self.rawValue
}
}
This means that you can omit the declaration of name in MyEnum, but not all.
For the part of the question we need to extend Eventable protocol where Self inherits RawRepresentable
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
extension Eventable where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
static var all: [MyEnum] = [bla, blu]
}
For the second part of your question we need to configure the function to handle a generic type
I would suggest making the function generic as well here is an example
func printEnum<T: Eventable>(_ event: T) {
print(event.name)
}
and we can use it for any object confirms to Eventable
printEnum(MyEnum.bla)
I have the following enum
enum Properties: CustomStringConvertible {
case binaryOperation(BinaryOperationProperties),
brackets(BracketsProperties),
chemicalElement(ChemicalElementProperties),
differential(DifferentialProperties),
function(FunctionProperties),
number(NumberProperties),
particle(ParticleProperties),
relation(RelationProperties),
stateSymbol(StateSymbolProperties),
symbol(SymbolProperties)
}
and the structs all look like this
struct BinaryOperationProperties: Decodable, CustomStringConvertible {
let operation: String
var description: String { return operation }
}
So how do I make that enum conform to CustomStringConvertible? I tried with a simple getter but obviously that calls itself and I'd like to call the specific struct's instead.
Bonus points: does an enum defined like that have a name?
Such an enum is called enum with associated values.
I'd implement description by manually switching over the cases:
extension Properties: CustomStringConvertible {
var description: String {
switch self {
case .binaryOperation(let props):
return "binaryOperation(\(props))"
case .brackets(let props):
return "brackets(\(props))"
...
}
}
}
Edit: an alternative is to use Swift's Mirror reflection API. The enum case of an instance is listed as the mirror's child and you can print its label and value like this:
extension Properties: CustomStringConvertible {
var description: String {
let mirror = Mirror(reflecting: self)
var result = ""
for child in mirror.children {
if let label = child.label {
result += "\(label): \(child.value)"
} else {
result += "\(child.value)"
}
}
return result
}
}
(This is a generic solution that should be usable for many types, not just enums. You'll probably have to add some line breaks for types that have more than a single child.)
Edit 2: Mirror is also what print and String(describing:) use for types that don't conform to Custom[Debug]StringConvertible. You can check out the source code here.