I'd like to use dart enums for more flow control outside of the enum class and allow the compiler to catch any missing enum cases in that switch.
I'm coming from Swift where enums can be defined and then later using the enum value coming in for force compiler flow control. So when developers add a new enum, they can update each location with the specific code flow.
I don't see any easy option to allow this type of programming.
This example is using MQTT topics with subscribes and I'd like to add code in all code locations where the enum is used.
enum SubscribeTopicType {
configData,
statusSensors,
}
So example above, I added "statusSensors" to the enum.
Inside the enum with an extension, the compiler catches any missing new enum types added.
extension SubscribeTopicTypeExtension on SubscribeTopicType {
String get endpointValue {
switch (this) {
case SubscribeTopicType.configData: return "data/configurationData/";
case SubscribeTopicType.statusSensors: return "data/sensorData/";
}
}
But when using the enum outside of the enum class or extension, the compiler doesn't find the missing case.
MqttManager class:
void _processSubscribePayload(SubscribeTopicType subscribeTopicType, String payload) {
switch (subscribeTopicType) {
case SubscribeTopicType.configData:
_subscribeConfigData(payload);
break;
// the code commented out will stop the compiler from requiring the new enum value.
// case SubscribeTopicType.statusSensors:
// _subscribeStatusSensors(payload);
// break;
}
}
Is this a gap or missing feature in dart? Or is there a way to force code flow control with enums?
Related
Is there a better approach to tackle this situation.
I am following MVVM for my project and we have ViewModels for every ViewController, which feed them the required data. The data is passed back through enums with associatedValues.
Suppose, we have
enum HomeViewModelState {
case failure(String)
case anotherState(SomeModel)
}
enum ListingViewModelState {
case failure(String)
case anotherState(SomeModel)
}
As we see we have a repetitive case i.e. failure(String) that pass back the error message incase of an api failure. I want to avoid this repetitive task. Is there a better approach ?
Consider using the Result instead of multiple enums, you will have a state stored as Result<SomeModel, Error> in every controller (and each controller can have a different model. The state assignment is convenient, you are no longer passing strings as errors, and will use proper Error protocol instead:
var state: Result<SomeModel1, Error>
...
state = .failure(MyCustomErrors.badThingHappened)
state = .success(SomeModel)
enum MyCustomErrors: Error {
case badThingHappened
It's still an enum and can be used in a switch statement:
switch state {
case .success(let model):
...
case .failure(let error):
...
}
You could create one state enum that uses generics:
enum ViewModelState<T> {
case failure(String)
case anotherState(T)
}
I am trying to construct a UI element locator strategy for our XCUITests in swift and having troubles with my code snippet. This strategy will be used in all the page classes and will reduce lots of repetitive code in our UI tests.
We are trying to implement a page object model in our tests so we can have a clear implementation of Page structure and the corresponding actions on the page. We have two different apps with the same functionality but the element ID might change on some pages.
e.g, App A's login button's id will be aLoginButton and App B's id will be bLogin button.
I have thought about an implementation which is showed below but having few issues with it.
Raw value for enum case must be a literal. I understand enums return value should be string or an integer literal and that raises my question number 2.
Am I doing it right ? Is there a better way to do this ?
In the code below, LocatorStrategy will be on it's own file and will serve in the individual page extensions to hold/return UI element location values
P.S I am new to Swift.
class BasePage {
func qcom_rcom(qcomId: String , rcomId: String) -> String {
if QCOM
return qcomId
else
return rcomId
}
}
class LoginPage : BasePage {
override init() {
super.init()
print(Locators.menuItemName.identifier)
}
}
extension LoginPage {
enum Locators: String, LocatorStrategy {
case menuItemName = qcom_rcom("aLoginButton", "bLoginButton") // getting error here
}
}
public protocol LocatorStrategy {
var identifier: String { get }
}
public extension LocatorStrategy where Self: RawRepresentable, Self.RawValue == String {
var identifier: String {
return rawValue
}
}
LoginPage.init()
Expected : The enum class case needs to hold two sets of data for the element on a need basis if the UIelement id is different on two different apps. I don't want to create two separate case items aMenuItemName and bMenuItemName to resolve this issue.
One way to do this is to use preprocessor directives.
For example,
enum Locators: String, LocatorStrategy {
#if QCOM
case menuItemName = "aLoginButton"
#elseif RCOM
case menuItemName = "bLoginButton"
#endif
}
And then you can change whether it is QCOM or RCOM in your build settings:
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?
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()
}
}
}
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.