How to get all values from nested enums? - swift

Working with my key/value Strings file, I'm looking for a simpler way of pulling out values by passing an enum into a function, which does the lookup and returns that value.
This Strings file can be updated over the network, so my app ships with a Default version. Currently, to get those defaults into memory I iterate over my enums cases and add the strings to a dictionary.
It looks like this:
enum StringKeys: String, CaseIterable {
case string1
case string2
case string3
}
func setupDefaults() {
for key in StringKeys.allCases {
defaults[key] = stringFile.value(forKey: key)
}
}
func getValue(for: StringKeys) -> String {
// stuff here...
}
This has all been fine for a while now, however this enum is getting quite large and so autocomplete is less helpful.
What I would like to have is something more like this:
enum StringKeys: CaseIterable {
enum Feature1: String, CaseIterable {
case title1
case button1
}
enum Feature2: String, CaseIterable {
case title3
case toggle1
}
}
Then call my getValue function with:
titleLabel.text = Strings.getValue(for: StringKeys.Feature1.title1)
This is all totally fine too, but I can't work out how to iterate over these nested keys to build the defaults dictionary in a nice clean way.
I tried extending CaseIterable to add a little allCasesRecursive function. Which Swift wouldn't allow.
I know this can be done by adding a property inside each enum, returning allCases, and combining them together at each parent. But that's still a bit messy.
The other option is to not use the enums to set up defaults, and instead just parse the whole file and put that directly into defaults, but I'd much rather the enums decide what goes in the defaults. This also makes it easy to see if any are missing i.e, the default for that key won’t be found, and I can throw an error.
So, basically I want to turn a bunch of nested enums into a flat dictionary. Even just an array of keys would be enough to get me the rest of the way. Is it possible to recursively drill down through nested enums? Could I use Reflection to solve this problem?
I'm also open to other ideas, otherwise I'll just stick with the giant, flat enum.

I don't know if i understand your question correctly, maybe this will help
// Create a protocol
protocol StringKeyValue {}
enum StringKeys: CaseIterable {
enum Feature1: String, CaseIterable, StringKeyValue {
case title1
case button1
}
enum Feature2: String, CaseIterable, StringKeyValue {
case title3
case toggle1
}
}
func getValue(for: StringKeyValue) -> String {
return "hi there"
}
// Maybe this's what you want?
titleLabel.text = getValue(for: StringKeys.Feature1.title1)

Related

How to check type of Swift object, produced by factory method?

I have an array of TemplateASTNode objects:
enum TemplateASTNode {
case blockNode(Block)
case sectionNode(Section)
case textNode(String)
// Define structs instead of long tuples
struct Block {
let innerTemplateAST: TemplateAST
let name: String
}
struct Section {
let tag: SectionTag
let expression: Expression
let inverted: Bool
}
// Factory methods
static func block(innerTemplateAST: TemplateAST, name: String) -> TemplateASTNode {
return .blockNode(Block(innerTemplateAST: innerTemplateAST, name: name))
}
static func section(templateAST: TemplateAST, expression: Expression, inverted: Bool, openingToken: TemplateToken, innerTemplateString: String) -> TemplateASTNode {
let tag = SectionTag(innerTemplateAST: templateAST, openingToken: openingToken, innerTemplateString: innerTemplateString)
return .sectionNode(Section(tag: tag, expression: expression, inverted: inverted))
}
static func text(text: String) -> TemplateASTNode {
return .textNode(text)
}
}
Objects are constructed with different factory methods — section, text, block, but in debugger actual class is TemplateASTNode.
How to check actual type of TemplateASTNode instance — Section, Text, Block?
How exacly this mechanism works? Need a start point what to google.
Swift magic seems a bit confusing for me:
we call factory method section()
it calls case sectionNode — I suppose it's a pointer to Enum attribute/choice
sectionNode holds a pointer to constructor Section which is a private structure inside TemplateASTNode
I expect a struct of type Section should be instantiated, but the generic TemplateASTNode one is produced. Why?
Why TemplateASTNode is Created
So the functions are actually using a case of the enum, this case could be .textNode but that case requires a string, which you give it.
So essentially the TemplateASTNode is acting as a wrapper to centralise all these models into one place, and your actual Structs are being held within the enum cases
Therefor you get given back a TemplateASTNode, which in this case could either be a .textNode .blockNode or .sectionNode each containing their respective data.
How to distinguish between them
As we've just established, these are all cases therefore we can use something called a switch. To sum up what this does, it essentially allows you to give it an enum, then add logic depending on which case it is. So for you, you'd want something like this:
switch templateASTNode { // These should be the variable and not the class
case .textNode(let text):
// Perform logic for text node
case .sectionNode(let section):
// Perform logic for section node
case .blockNode(let block):
// Perform logic for block node
}

Sub enumeration rawValue

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))).

How do I combine the safety of type requirements with the safety of enums?

So I'm working on an sql database abstraction layer in swift and I want to write it as safe as possible. By that I mean I would prefer to make it impossible for anybody to do anything illegal in the database using my library.
One thing I'm not fully able to solve yet is how to make the conversion from Swift types to SQL data types 100% safe. So I'll explain my current implementation and its flaws:
So in SQL, many data types have parameters. For example, when you want to define a column as a VARCHAR, you need to give it a parameter that represents the max length of the VARCHAR, like VARCHAR(255). You could say that VARCHAR is one of SQL's primitive data-types and VARCHAR(255) is a more specified data-type. To represent that in a type-safe way in swift I created two protocols:
public protocol PrimitiveDataType {
associatedtype Options = Void
}
public protocol SpecifiedDataType {
associatedtype Primitive: PrimitiveDataType
static var options: Primitive.Options { get }
static func from(primitive: Primitive) -> Self
var primitive: Primitive { get }
}
So here's an example of a PrimitiveDataType:
extension String: PrimitiveDataType {
public enum DatabaseStringType {
case char(length: Int), varchar(limit: Int), text
}
public typealias Options = DatabaseStringType
}
And here's an example of a SpecifiedDataType:
struct Name {
let value: String
init?(_ value: String) {
guard case 0...50 = value.characters.count else {
return nil
}
self.value = value
}
}
extension Name: SpecifiedDataType {
static let options = String.DatabaseStringType.varchar(limit: 50)
static func from(primitive: String) -> Name {
return Name(primitive)!
}
var primitive: String {
return value
}
}
Now I can use the information elsewhere in the library to know what kind of database-column is expected whenever a Name type is requested. This gives my library the power to make sure the database columns are always of the correct type.
I haven't written all that code yet, but it might look something like this:
func createColumn<SDT: SpecifiedDataType>(ofType type: SDT) -> String {
switch SDT.options {
case let options as String.DatabaseStringType:
switch options {
case .text:
return "TEXT"
case .char(let length):
return "CHAR(\(length))"
case .varchar(let limit):
return "VARCHAR(\(limit))"
}
case let length as UInt32.Options:
// return something like UNSIGNED INTEGER(\(length)) or whatever
// case etcetera
}
}
There is just one small problem with this. The set of valid primitive data-types is limited, but the set of possible implementations of the PrimitiveDataType protocol is unlimited, because I can't stop anybody from creating their own implementation of this protocol.
So for this reason it would be better to use some kind of enum instead of a number of implementations of a protocol. Because nobody can extend an enum that I create, so I can keep the options limited to whatever I define as valid types. However, an enum would create another problem that doesn't exist in my current implementation.
You see in my current implementation, whenever someone implements SpecifiedDataType with their options defined like so:
static let options = String.DatabaseStringType.varchar(limit: 50)
I can trust that the compiler will force them to make their type compatible with string by implementing the from(primitive: String) method and the primitive: String (computed) variable. AFAIK, if I switch the protocol for an enum I lose this compiler-enforced type-safety.
So in summary, I want to have all of the following:
A way for a developer to declare what sql data-type they want their type to correspond with.
A way to then force that developer to actually make their type compatible with that sql data-type.
To guarantee that the developer using my library can't somehow extend the list of sql-data types, but can only use the ones that I have defined for him / her.
So far I only know how to do either the first two, but not the last one, or the last one, but not the first two. How do I get all three?

Associating a type with a Swift enum case?

I’m using Swift 2, and I’d like to associate a struct type with each case in an enum.
At the moment, I’ve solved this by adding a function to the enum called type which returns an instance of the relevant type for each case using a switch statement, but I’m wondering if this is necessary. I know you can associate strings, integers etc. with a Swift enum, but is it possible to associate a type, too? All structs of that type conform to the same protocol, if that helps.
This is what I’m doing now, but I’d love to do away with this function:
public enum Thingy {
case FirstThingy
case SecondThingy
func type() -> ThingyType {
switch self {
case .FirstThingy:
return FirstType()
case .SecondThingy:
return SecondType()
}
}
}
I think you are saying that you want the raw value to be of type ThingyType, but that is not possible.
What you could do is make type a computed property, to remove the () and only needing to access it with thingy.type.
public enum Thingy {
case FirstThingy
case SecondThingy
var type: ThingyType {
switch self {
case .FirstThingy:
return FirstType()
case .SecondThingy:
return SecondType()
}
}
}

Swift constants: Struct or Enum

I'm not sure which of both are better to define constants. A struct or a enum. A struct will be copied every time i use it or not? When i think about a struct with static let constants it makes no sense that it will copied all the time, in my opinion. But if it won't copied then it doesn't matter what I take?
What advantages does the choice of a struct or enum?
Francescu says use structs.
Ray Wenderlich says use enums. But I lack the justification.
Both structs and enumerations work. As an example, both
struct PhysicalConstants {
static let speedOfLight = 299_792_458
// ...
}
and
enum PhysicalConstants {
static let speedOfLight = 299_792_458
// ...
}
work and define a static property PhysicalConstants.speedOfLight.
Re: A struct will be copied every time i use it or not?
Both struct and enum are value types so that would apply to enumerations as well. But that is irrelevant here
because you don't have to create a value at all:
Static properties (also called type properties) are properties of the type itself, not of an instance of that type.
Re: What advantages has the choice of a struct or enum?
As mentioned in the linked-to article:
The advantage of using a case-less enumeration is that it can't accidentally be instantiated and works as a pure namespace.
So for a structure,
let foo = PhysicalConstants()
creates a (useless) value of type PhysicalConstants, but
for a case-less enumeration it fails to compile:
let foo = PhysicalConstants()
// error: 'PhysicalConstants' cannot be constructed because it has no accessible initializers
Here's a short answer: Do your constants need to be unique? Then use an enum, which enforces this.
Want to use several different constants to contain the same value (often useful for clarity)? Then use a struct, which allows this.
One difference between the two is that structs can be instantiated where as enums cannot. So in most scenarios where you just need to use constants, it's probably best to use enums to avoid confusion.
For example:
struct Constants {
static let someValue = "someValue"
}
let _ = Constants()
The above code would still be valid.
If we use an enum:
enum Constants {
static let someValue = "someValue"
}
let _ = Constants() // error
The above code will be invalid and therefor avoid confusion.
Using Xcode 7.3.1 and Swift 2.2
While I agree with Martin R, and the Ray Wenderlich style guide makes a good point that enums are better in almost all use cases due to it being a pure namespace, there is one place where using a struct trumps enums.
Switch statements
Let's start with the struct version:
struct StaticVars {
static let someString = "someString"
}
switch "someString" {
case StaticVars.someString: print("Matched StaticVars.someString")
default: print("Didn't match StaticVars.someString")
}
Using a struct, this will match and print out Matched StaticVars.someString.
Now lets consider the caseless enum version (by only changing the keyword struct to enum):
enum StaticVars {
static let someString = "someString"
}
switch "someString" {
case StaticVars.someString: print("Matched StaticVars.someString")
default: print("Didn't match StaticVars.someString")
}
You'll notice that you get a compile time error in the switch statement on the case StaticVars.someString: line. The error is Enum case 'someString' not found in type 'String'.
There's a pseudo-workaround by converting the static property to a closure that returns the type instead.
So you would change it like this:
enum StaticVars {
static let someString = { return "someString" }
}
switch "someString" {
case StaticVars.someString(): print("Matched StaticVars.someString")
default: print("Didn't match StaticVars.someString")
}
Note the need for parentheses in the case statement because it's now a function.
The downside is that now that we've made it a function, it gets executed every time it's invoked. So if it's just a simple primitive type like String or Int, this isn't so bad. It's essentially a computed property. If it's a constant that needs to be computed and you only want to compute it once, consider computing it into a different property and returning that already computed value in the closure.
You could also override the default initializer with a private one, and then you'll get the same kind of compile time error goodness as with the caseless enum.
struct StaticVars {
static let someString = "someString"
private init() {}
}
But with this, you'd want to put the declaration of the struct in its own file, because if you declared it in the same file as, say, a View Controller class, that class's file would still be able to accidentally instantiate a useless instance of StaticVars, but outside the class's file it would work as intended. But it's your call.
In the Combine framework, Apple has chosen to prefer enums for namespaces.
enum Publishers
A namespace for types that serve as publishers.
enum Subscribers
A namespace for types that serve as subscribers.
enum Subscriptions
A namespace for symbols related to subscriptions.