Idiomatic Way to Test Swift Optionals

Idiomatic way to test Swift Optionals

I'm not sure how useful these methods are, but they make a fine starting point for discussion.

Take a look at the current implementation of Optional:

https://github.com/apple/swift/blob/ecd3c07a86394aa6b1372f1370f470842b39ea6e/stdlib/public/core/Optional.swift

Near the top, you can see that its own most primitive representation uses .None and .Some. So Solution 1 is the most direct approach, has the least overhead, and follows the pattern used in the implementation of Optional itself. I'd say that counts as idiomatic.

Using operator == just adds unnecessary indirection and wouldn't even work the way you presented it.

Swift idiomatic way to guard both aren't nil

A guard is an if, really, so you can do this the same way. This is clear and uses guard, which seems to be part of the fun. I present OR and AND options so you can pick one.

func doItOr(a: Int?, b:Int?) {
guard (a != nil || b != nil) else { return }
print("either A and B is not nil");
}

func doItAnd(a: Int?, b:Int?) {
guard (a != nil && b != nil) else { return }
print("both A and B are not nil");
}

doItOr(nil, b: nil)
doItOr(nil, b: 5)
doItOr(4, b: 5) // prints

doItAnd(nil, b: nil)
doItAnd(nil, b: 5)
doItAnd(nil, b: nil)
doItAnd(4, b: 5) // prints

In Swift, how can you test if an object implements an optional protocol method which differs by signature without actually calling that method?

As also shown in How do I resolve "ambiguous use of" compile error with Swift #selector syntax?, you can explicitly coerce a function reference to its expected type in order to resolve such ambiguities.

The only difference being, as such function references are to @optional protocol requirements done through optional chaining, you need to coerce to the optional type of the function. From there, you can do a comparison with nil in order to determine if both the delegate is non-nil, and it implements the given requirement.

For example:

import Foundation

@objc public protocol TestDelegate : AnyObject {
@objc optional func test()

// Need to ensure the requirements have different selectors.
@objc(testWithString:) optional func test(with string: String)
@objc(testWithInt:) optional func test(with int: Int)
}

class C : TestDelegate {
func test() {}
func test(with someString: String) {}
func test(with someInt: Int) {}
}

var delegate: TestDelegate? = C()

if delegate?.test as (() -> Void)? != nil {
print("supports 'test'")
}

if delegate?.test(with:) as ((String) -> Void)? != nil {
print("supports 'test w/ String'")
}

if delegate?.test(with:) as ((Int) -> Void)? != nil {
print("supports 'test w/ Int'")
}

// supports 'test'
// supports 'test w/ String'
// supports 'test w/ Int'

Note that I've given the test(with:) requirements unique selectors in order to ensure they don't conflict (this doesn't affect the disambiguation, only allowing class C to conform to TestDelegate).

Determine if Any.Type is Optional

Assuming that what you are trying to do is something like this:

let anyType: Any.Type = Optional<String>.self
anyType is Optional<Any>.Type // false

Sadly swift currently (as of Swift 2) does not support covariance nor contravariance and type checks directly against Optional.Type cannot be done:

// Argument for generic parameter 'Wrapped' could not be inferred
anyType is Optional.Type // Causes error

An alternative is to make Optional extend an specific protocol, and check for that type:

protocol OptionalProtocol {}

extension Optional : OptionalProtocol {}

let anyType: Any.Type = Optional<String>.self
anyType is OptionalProtocol.Type // true

Downcasting and optional: is this code idiomatic?

This is the Swift way (it is both safe and nice):

if (response as? NSHTTPURLResponse)?.statusCode == 200 {

It uses conditional casting and optional chaining to both test that the class is NSHTTPURLResponse and that the statusCode is 200. Note that you don't need ! after NSHTTPURLResponse.

If response is another class, then (response as? NSHTTPURLResponse) will return nil, thus the entire optional chain will return nil and since nil != 200 the test will fail.

Check if string contains optional string in Swift, but only if it's not nil

There is a map on Optional that executes the provided closure only if the optional is not nil. You can use map along with the nil coalescing operator ?? to provide the default value of true if the map returns nil because the filter is nil:

func process(items: [String], filter: String?) {
for item in items {
if filter.map(item.contains) ?? true {
// process item
}
}
}

how to compare value in swift optional

I presume you mean that you would like to test for someString != nil and someString != "a" in a single logical expression (and not two in and).

No, I don't think that's possible using the built in operators, but doable implementing a String extension like this:

extension String {
func isDifferentThan(value: String?) -> Bool {
return value != nil && self != value?
}
}

and you can use as follows:

someString = nil
"a".isDifferentThan(someString) // Return false

someString = "b"
"a".isDifferentThan(someString) // Return true

someString = "a"
"a".isDifferentThan(someString) // Return false

Addendum: A more elegant solution is to define your own logical operator. I have used !~= but feel free to use your own.

infix operator !~= { associativity left }
func !~= (a: String?, b: String?) -> Bool {
if a == nil || b == nil {
return false
}

return a != b
}

Tested as follows:

someString = nil
someString !~= "a" // Returns false

someString = "b"
someString !~= "a" // Returns true

someString = "a"
someString !~= "a" // Returns false

someString = nil
someString !~= nil // Returns false

You can fine tune it when dealing with nil values (such as add a check for both having nil and returning true, in case you want the condition "both sides are nil" to evaluate to true)

Idiomatic way to unwrap an integer string input

There are two optionals at play here.

First, readLine(strippingNewline: true) is optional. It can return nil if there's no input recieved prior to the End of File (EOF) character being received. It must be unwrapped before being passed into Int()

Secondly, Int() is optional, because the String it was given may not be a valid string representation of a number.

Do not use -1 in Swift to represent "no value". This is called a sentinel value, and it's exactly what optionals are invented to prevent. How do you distinguish between a -1 meaning "no/invalid input", and a -1 meaning "the user's input was -1?

Here is how I would write this code:

guard let userInput = readLine(strippingNewline: true) else {
// If we got to here, readLine(strippingNewLine:) returned nil
fatalError("Received EOF before any input was given")
}

// If we got to here, then userInput is not nil

if let n = Int(userInput) {
// If we got to here, then userInput contained a valid
// String representation of an Int
print("The user entered the Int \(n)")
}
else {
// If we got to here, then userInput did not contain a
// valid String representation of an Int.
print("That is not a valid Int.")
}

Check for non-optional values for nil in Swift

Even Apple's APIs sometimes return nil for a type that is not marked in the API as Optional. The solution is to assign to an Optional.

For example, for a while traitCollectionDidChange returned a UITraitCollection even though it could in fact be nil. You couldn't check it for nil because Swift won't let you check a non-Optional for nil.

The workaround was to assign the returned value immediately to a UITraitCollection? and check that for nil. That sort of thing should work for whatever your use case is as well (though your mail example is not a use case, because you're doing it wrong from the get-go).



Related Topics



Leave a reply



Submit