How to Say "If X == a or B or C" as Succinctly in Swift as Possible

any way to chain == and || operands

Try this:

if [a, b, c].contains(foo) {
//do stuff
}

Swift Best Pracices for compare a enum key to various key


switch (keyType) {
case .keyA,.keyB,.keyStart :
//do something
default :
//do something else
}

What is the best way to determine if a string contains a character from a set in Swift

You can create a CharacterSet containing the set of your custom characters
and then test the membership against this character set:

Swift 3:

let charset = CharacterSet(charactersIn: "aw")
if str.rangeOfCharacter(from: charset) != nil {
print("yes")
}

For case-insensitive comparison, use

if str.lowercased().rangeOfCharacter(from: charset) != nil {
print("yes")
}

(assuming that the character set contains only lowercase letters).

Swift 2:

let charset = NSCharacterSet(charactersInString: "aw")
if str.rangeOfCharacterFromSet(charset) != nil {
print("yes")
}

Swift 1.2

let charset = NSCharacterSet(charactersInString: "aw")
if str.rangeOfCharacterFromSet(charset, options: nil, range: nil) != nil {
println("yes")
}

Can you define an enum to represent values explicitly known to your app, but still handle unknown values decoded from the backend?


TL:DR - The Solution

For those who just want to see the solution, here it is in its entirety. This allows you to define an enum with known cases, but which can handle any raw value thrown at it at runtime, and do so in a lossless way for re-encoding purposes.

enum FeatureFlag : RawRepresentable, CaseIterable, Codable {

typealias RawValue = String

case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)

static let allCases: AllCases = [
.allowsTrading,
.allowsFundScreener,
.allowsFundsTransfer
]

init(rawValue: RawValue) {
self = Self.allCases.first{ $0.rawValue == rawValue }
?? .unknown(rawValue)
}

var rawValue: RawValue {
switch self {
case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"
case let .unknown(value) : return value
}
}
}

The Explanation

As mentioned above, our app has a certain set of known feature flags. At first, one may define them like so.

enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}

Simple enough. But again, now any value defined with the type FeatureFlag can only handle one of those specific known types.

Now say thanks to a new feature in the backend, a new flag allowsSavings is defined and pushed down to your app. Unless you have manually written the decoding logic (or resorted to optionals), the decoder will fail.

But what if the enum can handle unknown cases automatically for you, and do so in a completely transparent way?

It can! The trick is to define one additional case, unknown with an associated value of type RawValue. This new case handles all the unknown types handed to it when being decoded or even re-encoded.

Let's start by updating our enum with the new unknown case.

enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
}

This of course throws a ton of compiler errors thanks to that new case. Because of it, both RawRepresentable and CaseIterable can no longer be automatically synthesized by the compiler, so we have to manually implement them ourselves. Let's start with...

Manually Implementing the CaseIterable protocol

This is the easiest of the two steps. Since this 'version' of our app only knows about the first three cases, we can safely ignore all others. As such, to satisfy the protocol, we define a static allCases property that only specifies those cases we do care about.

Of Note: The property type AllCases here is an alias for [FeatureFlag] or more succinctly [Self], which we get for free when conforming to CaseIterable.

static let allCases: AllCases = [
.allowsTrading,
.allowsFundScreener,
.allowsFundsTransfer
]

With the above, this satisfies the CaseIterable protocol. Let's move on to...

Manually Implementing the RawRepresentable protocol

This is a little more complex/verbose, but this is where the 'magic' happens.

Specifying the RawValue type

Normally, to indicate your enum can be represented by a raw value, you specify a data type after an enum's name. In actuality, this is shorthand for telling the compiler you are conforming your enum to the RawRepresentable protocol and setting a RawValue typealias for that data type. However, again, because of our unknown type having an associated value, the compiler can't do that implicitly, so we must do so explicitly.

To do so, replace your raw type with RawRepresentable in the definition, then manually set the RawValue typealias inside, like so...

enum FeatureFlag : RawRepresentable, CaseIterable, Codable {

typealias RawValue = String

case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
}

implementing the rawValue: property (rawValues matching the case names)

Next up, we have to implement the rawValue property. For known cases where the raw value matches the case name, the implementation is simple as we can just return String(describing: self) and for the unknown cases, we return the associated value. Here's that implementation

var rawValue: RawValue {

switch self {
case let .unknown(value) : return value
default : return String(describing: self)
}
}

implementing the rawValue: property (rawValues unrelated to the case names)

But what if we wanted to express different values from the case names, or even a different data type entirely? In that situation, we have to manually expand out the switch statement and return the appropriate values like so....

var rawValue: RawValue {

switch self {

case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"

case let .unknown(value) : return value
}
}

*Note: You must specify the raw values here and not up with the case definitions using the equals (=) as that's really syntactic sugar for the compiler to create what we're doing manually here, which again we have to since the compiler can't do it for us.

implementing init(rawValue:)... the 'Magic Sauce'

As mentioned, the entire purpose of this exercise is to allow your code to work with known types as usual, but to also gracefully handle unknown cases that are thrown at it. But how do we achieve that?

The trick is in the initializer, you first search for a known type within allCases and if a match is found, you use it. If however a match isn't found, rather than return nil like in the default implementation, you instead use the newly-defined unknown case, placing the unknown raw value inside.

This has the additional benefit of guaranteeing to always return an enum value from the initializer, so we can define it as a non-optional initializer as well (which is different from the implicitly created one from the compiler) making the call-site code easier to use as well.

Here's the implementation of the initializer:

init(rawValue: String) {

self = Self.allCases.first{ $0.rawValue == rawValue }
?? .unknown(rawValue)
}

In some situations, you may want to log when an unknown value is being passed for debugging purposes. That can be done with a simple guard statement (or an if if you prefer) like so...

init(rawValue: String) {

guard let knownCase = Self.allCases.first(where: { $0.rawValue == rawValue }) else {

print("Unrecognized \(FeatureFlag.self): \(rawValue)")
self = .unknown(rawValue)
return
}

self = knownCase
}

Interesting Behaviors on Equality and Hashability

One of the interesting things is that enums based on a raw value actually use that value for equality comparisons. Thanks to that piece of information, all three of these values are equal...

let a = FeatureFlag.allowsTrading              // Explicitly setting a known case
let b = FeatureFlag(rawValue: "allowsTrading") // Using the initializer with a raw value from a known case
let c = FeatureFlag.unknown("allowsTrading") // Explicitly setting the 'unknown' case but with a raw value from a known case

print(a == b) // prints 'true'
print(a == c) // prints 'true'
print(b == c) // prints 'true'

Additionally, if your raw value conforms to Hashable, you can make the entire enum conform to Hashable by simply specifying its conformance to that protocol.

extension FeatureFlag : Hashable {}

With that conformance, now you can use it in sets or as keys in a dictionary as well, which again, thanks to the rules of equality above, provides for some interesting, but logically expected, behaviors.

Again, using 'a', 'b' and 'c' as defined above, you can use them like so...

var items = [FeatureFlag: Int]()

items[a] = 42 // Set using a known case
print(items[a] ?? 0) // prints 42 // Read using a known case
print(items[b] ?? 0) // prints 42 // Read using the case created from the initializer with a raw value from the known case
print(items[c] ?? 0) // prints 42 // Read using the 'unknown' case but with a raw value from the known case

Side-Benefit: Lossless Encoding/Decoding

One often-overlooked/underappreciated side-benefit of this approach is that serilization/deserialization is lossless and transparent, even for unknown values. In other words, when your app decodes data containing values you don't know about, the unknown case is still capturing and holding them.

This means if you were to then re-encode/re-serialize that data back out again, those unknown values would be re-written identically to how they would be if your app did know about them.

This is incredibly powerful!

This means for instance if an older version of your app reads in data from a server containing newer, unknown values, even if it has to re-encode that data to push it back out again, the re-encoded data looks exactly the same as if your app did know about those values without having to worry about versioning, etc. They're just silently and happily passed back.

Summary

With the above in place, you can now encode or decode any string into this enumeration type, but still have access to the known cases you care about, all without having to write any custom decoding logic in your model types. And when you do 'know' about the new type, simply add the new case as appropriate and you're good to go!

Enjoy!

Swift: Testing optionals for nil

In Xcode Beta 5, they no longer let you do:

var xyz : NSString?

if xyz {
// Do something using `xyz`.
}

This produces an error:

does not conform to protocol 'BooleanType.Protocol'

You have to use one of these forms:

if xyz != nil {
// Do something using `xyz`.
}

if let xy = xyz {
// Do something using `xy`.
}

Sort Dictionary by keys


let dictionary = [
"A" : [1, 2],
"Z" : [3, 4],
"D" : [5, 6]
]

let sortedKeys = Array(dictionary.keys).sorted(<) // ["A", "D", "Z"]

EDIT:

The sorted array from the above code contains keys only, while values have to be retrieved from the original dictionary. However, 'Dictionary' is also a 'CollectionType' of (key, value) pairs and we can use the global 'sorted' function to get a sorted array containg both keys and values, like this:

let sortedKeysAndValues = sorted(dictionary) { $0.0 < $1.0 }
println(sortedKeysAndValues) // [(A, [1, 2]), (D, [5, 6]), (Z, [3, 4])]

EDIT2: The monthly changing Swift syntax currently prefers

let sortedKeys = Array(dictionary.keys).sort(<) // ["A", "D", "Z"]

The global sorted is deprecated.



Related Topics



Leave a reply



Submit