iOS RxSwift - How to unwrap an `Optional<Optional<T>>` or `T??`?

1.3k Views Asked by At

I'm using the logic below to check the state of my subject using RxBlocking. I'm getting a weird value of Int?? out of try? subject.verifier.toBlocking().first().

The syntax below pleases the compiler but makes my eyes bleed.

How do I get an unwrapped value out of RXBlocking expectation?

func checkSubjectState() -> Int
    {
      let updatedChecksum = try? subject.verifier.toBlocking().first() ?? 0
      return updatedChecksum ?? 0
    }

let expectedCheckSum = checkSubjectState()
expect(expectedCheckSum).to(equal(knownValue))
4

There are 4 best solutions below

0
On BEST ANSWER

Here's an answer that doesn't require you to define any of your own functions:

return (try? subject.verifier.toBlocking().first()).flatMap { $0 } ?? 0
0
On

I came up with another solution that I think you might like more:

infix operator ???: NilCoalescingPrecedence
func ???<T>(opt: T??, val: @autoclosure () -> T) -> T {
    return ((opt ?? val()) ?? val())
}

The above will allow you to simply:

return (try? subject.verifier.toBlocking().first()) ??? 0

I tried to give the operator a higher precedence than the try? so the parens wouldn't be needed, but I couldn't find a way to do that.

0
On

Yea, so first() can throw and it returns an Optional type, so try? first() returns an Optional<Optional<Int>> or Int??. Frankly, I think the first() function is poorly written. It should either throw or return optional, not both.

You could write a flattened operator:

public protocol OptionalType {
    associatedtype Wrapped
    var value: Wrapped? { get }
}

extension Optional: OptionalType {
    public var value: Wrapped? {
        return self
    }
}

extension Optional where Wrapped: OptionalType {
    var flattened: Wrapped.Wrapped? {
        switch self {
        case let .some(opt):
            return opt.value
        case .none:
            return nil
        }
    }
}

Which would allow you to do:

let expectedCheckSum = (try? subject.verifier.toBlocking().first()).flattened ?? 0

Whether you think it's worth it or not is your call.

0
On

There are two other alternatives avoiding the double optional by handling the error

The first hands over the error to the caller

func checkSubjectState() throws -> Int
{
  return try subject.verifier.toBlocking().first() ?? 0
}

The second adds a do - catch block

func checkSubjectState() -> Int
{
    do { return try subject.verifier.toBlocking().first() ?? 0 } 
    catch { return 0 }
}