Consider the following code, where I declared an enum with sub enums inside of it.
enum LocalizeKey {
case message(Messages)
case buttons(Buttons)
enum Buttons: String {
case remove = "Remove"
case add = "Add"
}
enum Messages: String {
case success = "Success"
case failure = "Failure"
}
}
In a normal enum with no sub enums we can easily access .rawValue property and get the raw value of whatever case we picked.
For this case, i created a function like this just to check out what am i getting .
func keyString(for type: LocalizeKey) {
print(type)
}
keyString(for: .message(.failure)) // usage
Problem : there are no other properties than .self to access for this LocalizeKey enum .
What I am trying to achieve: perhaps you can relate by the naming, i am trying to wrap my localized keys, so i can access them easily based on the key type etc, and the rawValue that is refering to the actual key will go into the getLocalizedValue function .
Playground Output : using the function above the playground output was
message(__lldb_expr_21.LocalizeKey.Messages.failure)
Edit: without having to create a variable that switches self on every case, imagine if we had +400 key that would be a huge mess probably.
You need to switch on the type parameter and do pattern matching:
switch type {
case .message(let messages): return messages.rawValue
case .buttons(let buttons): return buttons.rawValue
}
You can also make this an extension of LocalizeKey:
extension LocalizeKey {
var keyString: String {
switch self {
case .message(let messages): return messages.rawValue
case .buttons(let buttons): return buttons.rawValue
}
}
}
You are going to have to switch somewhere. If there are only a handful of "sub-enums", it is probably the easiest to just write a switch manually:
func keyString(for type: LocalizeKey) {
switch type {
case .message(let message):
print(message.rawValue)
case .buttons(let button):
print(button.rawValue)
}
}
If you don't want to write this manually, you either have to change your data structure so it is not needed, or use a code generation tool that generates the boilerplate for you.
Although The mentioned answers do provide the solution, I'd mention the issue of the approach itself:
At this point, each new case (key) has to be added in your switch statement with an associated value, which seems to be undesired boilerplate coding; I assume that you could imagine how it will look like when having many cases in the enums.
Therefore, I'd recommend to follow an approach to be more dynamic instead of adding the value of each case manually in a switch statement. Example:
protocol Localizable {
var value: String { get }
}
extension RawRepresentable where Self: Localizable, Self.RawValue == String {
var value: String { return rawValue }
}
extension CustomStringConvertible where Self: RawRepresentable, Self.RawValue == String {
var description: String { return rawValue }
}
struct LocalizeKey {
enum Buttons: String, Localizable, CustomStringConvertible {
case remove = "Remove"
case add = "Add"
}
enum Messages: String, Localizable, CustomStringConvertible {
case success = "Success"
case failure = "Failure"
}
}
We are applying the same logic for your code, with some improvements to make it easier to maintain.
Based on that, you still able to implement your function as:
func keyString(for type: Localizable) {
print(type)
}
Usage:
keyString(for: LocalizeKey.Buttons.add) // Add
keyString(for: LocalizeKey.Messages.success) // Success
IMO, I find calling it this way seems to be more readable, straightforward rather than the proposed approach (keyString(for: .message(.failure))).
Related
enum ImportType: Int {
case First = 1
case None
case Original
}
var type: ImportType = .First
print(type) --------------------> This will output "First"
NSLog("%#", String(type) --------------------> I can't do this.
NSLog("%d", type.rawValue) --------------------> This will output "1"
Hi All,
I want to get similar result of NSLog as the print function, it more readable for people, but I can't found a way to do this, I got some result that need to do additional handling inside the enum, but I am using other body's source code and just want to collect some information directly.
Are there easy transform way to do what I want?
Thanks~~
Eric
print uses String.init(describing:) under the hood to convert whatever you give it to a String, so you can do that too:
NSLog("%#", String(describing: type))
But really though, the enum should conform to CustomStringConvertible:
enum ImportType: Int, CustomStringConvertible {
case First = 1
case None
case Original
var description: String {
switch self {
case .First:
return "First"
case .None:
return "None"
case .Original:
return "Original"
}
}
}
and you should not rely on this default behaviour of String(describing:), because its behaviour is not specified unless the type conforms to TextOutputStreamable, CustomStringConvertible or CustomDebugStringConvertible. See here for more info.
I try to write less code in following scenario:
I have this Queryable protocol and a Parameter enum:
protocol Queryable {
var urlQuery: URLQueryItem { get }
}
enum PaginationParameter: Queryable {
case page(Int)
case pageSize(Int)
var queryItem: URLQueryItem {
switch self {
case .page(let page):
return URLQueryItem(name: "page", value: page.description)
case .pageSize(let pageSize):
return URLQueryItem(name: "page_size", value: pageSize.description)
}
}
}
And an enum that provides some default cases and some specific cases defined by a generic type:
enum Parameter<P: Queryable> {
case pagination(PaginationParameter)
case specific(P)
}
Example Usage
enum BookParameters: Queryable {
case search(String)
case id(Int)
var urlQuery: URLQueryItem {
switch self {
case .search(let string):
return URLQueryItem(name: "search", value: string)
case .id(let id):
return URLQueryItem(name: "id", value: id.description)
}
}
}
let parameters: [Parameter<BookParameters>] = [
.pagination(.pageSize(10)),
.specific(.id(123))
]
Now I need to get the url query item through both .pagination and .specific cases.
let queryItems = parameters.map({
switch $0 {
case .pagination(let param):
return param.queryItem
case .specific(let param):
return param.queryItem
}
})
It would be nice to have a way to handle the nested cases combined since they conform to the same protocol. That doesn't work since I have to go to the nested cases through the parent cases:
A small improvement would be to bury the switch statement in an extension for the Parameters enum and let it conform to the Queryable protocol as well:
extension Parameters: Queryable {
let queryItem: URLQueryItem {
switch self {
case .pagination(let param):
return param.queryItem
case .specific(let param):
return param.queryItem
}
}
}
That results in a one liner but I have only shifted my problem to a different place.
let queryItems = parameters.map({ $0.queryItem })
Since you are using nested enums with associated values, I don't really see a way to avoid having this extra switch on the top level Parameter enum. As far as I am concerned, Swift doesn't provide us with a tool to work with cases in such a way where we could cast all cases with the "same" associated value types to a single case. What you could do is to rethink the existence of Parameter type, since it doesn't seem to be really useful due to the fact you still need to refer to it as Parameter<BookParameters> or Parameter<SomeOtherTypeThatConformsToQueryable>.
Personally I would skip the top level enum, and refer to the parameters property type as [Queryable] directly.
var parameters: [Queryable] = [
PaginationParameter.pageSize(10),
BookParameters.id(123)
]
Makes things much more simpler and easier to reason about. Also there is now a way to add other cases of other types, where it would not be possible with your initial solution.
enum SomeOtherTypeThatConformsToQueryable: Queryable {
case aVeryNiceCase(Int)
}
parameters.append(SomeOtherTypeThatConformsToQueryable.aVeryNiceCase(0))
// Appending this to array of type `[Parameter<BookParameters>]`, would not be
// possible without explicitly adding new case to the `Parameter` enumeration
Also if you find yourself calling the map { $0.queryItem } often, you could provide an extension to the Array where Element is type of Queryable
extension Array where Element == Queryable {
var queryItems: [URLQueryItem] { return map { $0.queryItem } }
}
// And now you can simply call
let queryItems = parameters.queryItems
Without conforming Parameters to Queryable, you can just introduce a variable in Parameters to get the queryItem because both cases accept a type that already conform to Queryable,
enum Parameter<P: Queryable> {
case pagination(PaginationParameter)
case specific(P)
var urlQuery: URLQueryItem {
switch self {
case .pagination(let param):
return param.urlQuery
case .specific(let param):
return param.urlQuery
}
}
}
Let's say we have simple enum with message types:
enum MessageType {
case audio
case photo
case text
}
There is Handler class which handles messages with specific types only:
class Handler {
let allowed: [MessageType]
init(_ allowed: [MessageType]) { self.allowed = allowed }
func canHandle(_ messageType: MessageType) -> Bool {
return allowed.contains(messageType)
}
}
Basic usage example:
let handler = Handler([.audio, .photo])
print(handler.canHandle(.text)) // Prints false
I want to upgrade my MessageType and add associated value for some of message types.
class Audio {}
enum MessageType {
case audio(Audio)
case photo
case text
}
Problem is that I can't store enum's pattern in allowed array for the future check in canHandle:
// error: '_' can only appear in a pattern or on the left side of an assignment
let handler = Handler([.audio(_), .photo])
Is it possible to resolve such case in a "clean" way?
It's not possible to modify MessageType because it's in third-party library (e.g. making arguments optional and passing nil)
It's not possible to init MessageType.audio(Audio) with fake Audio because it could have private initializer
I hope to avoid switch, case let or other hard-coded checks in canHandle
Any suggestions? Thanks
If instantiating the enum with a default (or garbage) value isn't a big deal
enum MessageType {
case audio(String)
case photo
case text
}
protocol SneakyEquatableMessage {
func equals(message: MessageType) -> Bool
}
extension MessageType: SneakyEquatableMessage {
func equals(message: MessageType) -> Bool {
switch (self, message) {
case (.audio(_), .audio(_)),
(.photo, .photo),
(.text, .text):
return true
default:
return false
}
}
}
class Handler {
let allowed: [MessageType]
init(_ allowed: [MessageType]) { self.allowed = allowed }
func canHandle(_ messageType: MessageType) -> Bool {
return allowed.contains { $0.equals(message: messageType) }
}
}
Basic Usage
let handler = Handler([.audio(""), .photo])
print(handler.canHandle(.text)) // Prints false
print(handler.canHandle(.audio("abc")) //Prints true
Default (or garbage) values are unrealistic
This particular section is more specific in this context, but ultimately you're going to have breakdown your enum somehow as of Swift 4. So this is my suggestion: Dependency Injection of the Factory Pattern inside Handler. This solves all of your problems pretty cleanly without having to touch switch within Handler or optionals.
enum DisassembledMessage {
case audio
case photo
case text
}
protocol MessageTypeFactory {
func disassemble(message: MessageType) -> DisassembledMessage
func disassemble(messages: [MessageType]) -> [DisassembledMessage]
}
class Handler {
let allowed: [MessageType]
let factory: MessageTypeFactory
init(allowed: [MessageType], with factory: MessageTypeFactory) {
self.allowed = allowed
self.factory = factory
}
func canHandle(_ messageType: DisassembledMessage) -> Bool {
return factory
.disassemble(messages: allowed)
.contains { $0 == messageType }
}
}
Basic Usage
let audioValue: Audio = //...
let audioMessage = MessageType.audio(audioValue)
let factory: MessageTypeFactory = //...
let handler = Handler(allowed: [audioMessage, .photo], with: factory)
print(handler.canHandle(.text)) // Prints false
print(handler.canHandle(factory.disassemble(message: audioMessage))) //Prints true
You may be asking: wait... you just created another enum (also this is just an example, you could convert it to whatever you want in that protocol). Well I say: the enum you're using is from a library... see my notes section. Also, you can now use that factory anywhere you need to break down the library type to something, including inside Handler. You could easily expand the protocol MessageTypeFactory to convert your enum to other types (hopefully behaviors) you've created, and basically just distance yourself from the library type when you need to. I hope this helps clarify what I was getting at! I don't even think you should store MessageType in your class. You should store your own type which is some kind of mapped version of MessageType, like DisassembledType.
I hope this helps!
Notes
A few things:
I'm sorry your soul is owned by a library, really.
Use the Adapter Pattern. Clean C++ is one of many places you
can learn about it. Don't pollute your entire code base with one of
their types! That's just a tip.
I know you said you didn't want to use a switch... but you're working with enums... At least it's within the enum!
Use your own types! (Did I say that?)
Use the Adapter Pattern! (Stop it.)
I've created an enum for Instagram endpoints with nested enums similar to Moya.
enum Instagram {
enum Media {
case Popular
case Shortcode(id: String)
case Search(lat: Float, lng: Float, distance: Int)
}
enum Users {
case User(id: String)
case Feed
case Recent(id: String)
}
}
I would like to return the path for each endpoint.
extension Instagram: TargetType {
var path: String {
switch self {
case .Media.Shortcode(let id):
return "/media/shortcode"
}
}
}
However I'm getting an error on the switch statement above for the path.
Enum case Shortcode is not a member of type Instagram
How to fix?
Advanced Practical Enums
I'm adding a more general answer for a few reasons.
This is the only open question regarding nested enums and switch statements. The other one is sadly closed.
The only legit answer does not show how to assign the value of a nested enum to a symbol. The syntax was not intuitive to me.
None of the other answers have extensive case examples.
An enum nested 3 levels deep is more illustrative of the required syntax. Using efremidze answer still took me a while to work it out.
enum Action {
case fighter(F)
case weapon(W)
enum F {
case attack(A)
case defend(D)
case hurt(H)
enum A {
case fail
case success
}
enum D {
case fail
case success
}
enum H {
case none
case some
}
}
enum W {
case swing
case back
}
}
// Matches "3 deep"
let action = Action.fighter(.attack(.fail))
// Matches "1 deep" because more general case listed first.
let action2 = Action.weapon(.swing)
switch action {
case .fighter(.attack(.fail)):
print("3 deep")
case .weapon:
print("1 deep")
case .weapon(.swing):
print("2 deep to case")
case .fighter(.attack):
print("2 deep to another enum level")
default:
print("WTF enum")
}
By adding an associated value for the nested enum you can access it using a switch statement.
enum Instagram {
enum MediaEndpoint {
case Search(lat: Float, lng: Float, distance: Int)
}
case Media(MediaEndpoint)
}
extension Instagram: TargetType {
var path: String {
switch self {
case .Media(.Search):
return "/media/search"
}
}
}
// Demo
protocol TargetType {
var path: String { get }
}
class MoyaProvider<Target: TargetType> {
func request(_ target: Target, completion: #escaping () -> ()) {}
}
let provider = MoyaProvider<Instagram>()
provider.request(.Media(.Search(lat: 0, lng: 0, distance: 0))) {}
There is a couple of problems with your architecture. You should know when and why you need to use extensions and protocols and how you should structure your blocks of code.
If your type needs to conform to that protocol, feel free to use it to
ensure you set your own standards. I don't even see that in the github project you referred to.
Extension are good way to have a primitive type and extend its functionality in other parts of the project. It doesn't make sense to me why you should extend the type right after declaration. A good use case of it is where the String type has been extended to support URL Encoded values:
private extension String {
var URLEscapedString: String {
return self.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())!
}
}
When you are using this type of switch-case block
switch self {
case .Zen:
return "/zen"
case .UserProfile(let name):
return "/users/\(name.URLEscapedString)"
case .UserRepositories(let name):
return "/users/\(name.URLEscapedString)/repos"
}
The value in the case should be a member of self. that's why it can not find the type. the type is declared inside Instagram enum but it doesn't hold value in the self. it holds value inside Media. So move your media related function into the declaration of Media and access them there. That way self is referring to Media. Here's the full working code for me:
private extension String {
var URLEscapedString: String {
return self.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())!
}
}
public enum Instagram {
public enum Media {
case Search(String)
var path:String {
switch self {
case Media.Search(let keyword):
return "/media/search/\(keyword.URLEscapedString)"
}
}
}
}
var me = Instagram.Media.Search("me")
print(me.path)
As a piece of advice, in each step of building your whole architecture just question yourself if that piece of code belongs to that type or should be accessible publicly. In this case it makes complete sense to move search to Media cause you are searching media. You can add the same pattern for something like User and have search under user that returns different value.
Enum case Search is not a member of type Instagram
As the compiler say, Search is not a member of type Instagram. It's just an enum in the scope of Instagram. You have to create a member that is an instance of Search in Instagram
struct Instagram {
enum Media {
case Search(lat: Float, lng: Float, distance: Int)
}
// something like:
var media = .Search(lat: 0, lng: 0, distance: 0)
// I'm not sure this one is right syntax
// because I can't check it right now.
// please just get the idea
}
extension Instagram: TargetType {
var path: String {
switch self.media {
case .Search(let _, let _, let _):
return "/media/search"
}
}
}
I have an enum as follows
enum AccountForm: String {
case Profile
enum Content: String {
case Feedback
case Likes
}
enum Actions: String {
case Redeem
case Help
}
}
This represents a form, where profile content and actions are sections and the cases are rows.
These resolve to strings and work as expected
AccountForm.Profile.rawValue returns "Profile"
AccountForm.Content.Feedback.rawValue returns "Feedback"
However, I'd like AccountForm.Content.rawValue to return "Content"
Is this possible? Or is there a better way besides enums to achieve this?
I'm guessing you've got an answer to this by now but just in case you didn't try this:
enum AccountForm : String {
case profile
enum Content: String {
static let rawValue = "Content"
case feedback = "Feedback"
case likes = "Likes"
}
enum Actions : String {
static let rawValue = "Actions"
case redeem = "Redeem"
case help = "Help"
}
}
Static properties on both the Content and Actions enumerations should achieve what you want. A word of warning though. By calling the properties rawValue you're obviously implying the returned values are raw values when technically they aren't. If you can I'd think of a better name (maybe sectionTitle?).
Couple of other things to note.
First, you have to define the properties as static as it sounds like you want to call them on the enumeration type (e.g. AccountForm.Content.rawValue) rather than on an individual enumeration case (e.g. AccountForm.Content.feedback.rawValue). I'll leave you to decide whether that makes sense in your context.
Secondly, when Swift 3.0 arrives, the recommendation for enumeration cases is going to be that case labels follow a lowerCamelCase naming convention rather than the UpperCamelCase convention that was recommended in Swift 2.2.
I've followed the Swift 3.0 recommendation here but the result is that explicit raw-value assignments is needed as you won't be able to rely on using the implicit raw-value assignment mechanism assigning a string with an UpperCamelCase representation which is kind of annoying but those are the implications.
Anyway, hope it helps.
enum Typo {
case Bold
case Normal
case Italic
case All
}
enum Format: CustomStringConvertible {
case Header(Typo)
case Text(Typo)
var description:String {
switch self {
case .Header(let value) where value != .All:
return "Header.\(value)"
case .Header(let value) where value == .All:
return "Header"
case .Text(let value) where value == .All:
return "Text"
case .Text(let value) where value != .All:
return "Text.\(value)"
default:
return ""
}
}
}
let a:Format = .Header(.Bold)
let b:Format = .Text(.Italic)
Format.Header(.All) // Header
Format.Text(.Bold) // Text.Bold
Format.Text(.All) // Text