3

I've got the following code, which illustrates a problem I haven't figured out how to solve cleanly, that is:

How can I make a function (isNil) that will return true for both nil, and Optional(nil), but false for anything else?

class Foo {
  var baz : Date? = nil
  subscript(key: String) -> Any? {
    get {
      let m = Mirror(reflecting: self)
      for child in m.children {
        if (child.label == key) { return child.value }
      }
      return nil
    }
  }
}

// this works unless the field is an Optional(nil)
func isNil(_ field: Any?) -> Bool {
  if field == nil { return true }
  return false
}

// this sort of works in a really terrible hacked way
func isNilViaString(_ field: Any?) -> Bool {
  if field == nil { return true }
  return "\(field.debugDescription)" == "Optional(nil)"
}

// this returns true as expected
print("isNil(nil) = \(isNil(nil))")
var optionalNil = Foo()["baz"]
// I'd like this to return true as well
print("isNil(optionalNil) = \(isNil(optionalNil))")
// this returns true, but is super hacky
print("isNilViaString(optionalNil) = \(isNilViaString(optionalNil))")
// this is an example of a problem with the isNilViaString method
print("isNilViaString(optionalNil) = \(isNilViaString("Optional(nil)"))")

2
  • I’m curious, how would you want to use this? There is this potentially similar question just yesterday stackoverflow.com/q/75174431/3141234 Commented Jan 20, 2023 at 13:02
  • I'm using it as part of an object wrapper, where I want to be able to iterate over all the members of a class and do stuff with them -- one of those things is comparing to another value, that may be nil, and I want the Optional(nil) to match that case. Your answer on that question is interesting though, thanks for the link :) Commented Jan 20, 2023 at 22:30

4 Answers 4

2

isNil is best based on flattening the optionality of the wrapped value. (You may not actually have a use for isNil if you incorporate this directly into your subscript.)


If you don't care about the unwrapping failure details:

public extension Any? {
  /// Represent an `Optional` with `Any?` instead of `Any`.
  ///
  /// If `any` is an optional, this instance will copy it.
  /// Otherwise, this instance will wrap it.
  ///
  /// - Note: Use this to avoid an `Any?` actually representing an `Any??`.
  init(flattening any: Any) {
    switch any {
    case let optional as Self:
      self = optional
    }
  }

  var isNil: Bool { flatMap(Self.init) == nil }
}
subscript(key: String) -> Any? {
  ( Mirror(reflecting: self).children
    .first { $0.label == key }?
    .value
  ).flatMap(_?.init)
}

If you do:

public extension Optional {
  /// Represents that an `Optional` was `nil`.
  struct UnwrapError: Error & Equatable {
    public init() { }
  }
}

public extension Any? {
  /// The wrapped value, whether `Wrapped` is an `Optional` or not.
  /// - Throws: `Any?.UnwrapError` when `nil`,
  ///   or  `Any??.UnwrapError` when wrapping another `Optional` that is `nil`.
  var doublyUnwrapped: Wrapped {
    get throws {
      switch self {
      case let doubleWrapped?? as Self?:
        return doubleWrapped
      case _?:
        throw Self?.UnwrapError()
      case nil:
        throw UnwrapError()
      }
    }
  }

  var isNil: Bool { (try? doublyUnwrapped) == nil }
}
subscript(key: String) -> Any {
  get throws {
    try (
      Mirror(reflecting: self).children
        .first { $0.label == key }?
        .value
    )
    .doublyUnwrapped
  }
}
Sign up to request clarification or add additional context in comments.

5 Comments

I love incorporating the solution directly into the subscript -- I'm fairly new to Swift though, and don't fully understand the code -- specifically the non-throwing code. I'm not clear on what the _? does -- and is there a way to represent that code with an explicit closure instead of flatmap(_?.init) -- also the init, what happens in the case where any is not an Any? -- or maybe I'm not even asking that correctly. I'm also a bit worried about monkey patching and breaking a 3rd party library (or SwiftUI or something) that relies on the Any??. I very much appreciate your help, thanks :)
Maybe a better way of asking part of that is this: "what happens to the any argument that gets passed in when it doesn't match the case statement - is it just discarded or is it somehow assigned to self implicitly or what?"
_ is just a type placeholder. You could also use Optional instead of _? (matching the Self.init above, where Self is equivalent to Optional), or fully qualify it with (Any?.init(flattening:)). Do not create a new closure though; inits are already closures.
There is no possibility of the any not matching that case. It either matches directly as an Optional (any is Any?), or it gets promoted to be one (as Optional works for every type).
Thanks again, I understand better now, and love your solution, I'm adapting it for my code. Also, correct me if I'm wrong, but it seems like there's no danger of breaking anything else that might be using Any?.init because this init will never match any other calls, at least in part due to the flattening keyword. I'm loving Swift, so thanks again for helping me understand it better.
2

You can convert them to AnyObject then compare with NSNull:

func isNil(_ field: Any?) -> Bool {
    return field as AnyObject is NSNull
}

Comments

1

Once you check if field is nil or not, you can then use a switch with a case that checks if the value is an optional.

Here is an updated isNil method:

func isNil(_ field: Any?) -> Bool {
    if let field {
        switch field {
        case let optional as Optional<Any>:
            if case .some = optional {
                // This was an Optional but not nil
                return false
            } else {
                // This was an Optional<nil>
                return true
            }
        default:
            // This was not an optional
            return false
        }
    } else {
        // nil was passed in
        return true
    }
}

Some updated test cases:

print("isNil(nil) = \(isNil(nil))")

var optionalNil = Foo()["baz"]
print("isNil(optionalNil) = \(isNil(optionalNil))")

var foo = Foo()
foo.baz = Date()
var optional = foo["baz"]
print("isNil(optional) = \(isNil(optional))")

print("isNil(Date()) = \(isNil(Date()))")

Output:

isNil(nil) = true
isNil(optionalNil) = true
isNil(optional) = false
isNil(Date()) = false

1 Comment

Thanks much, this is a great answer, and very easy to understand :)
-1

This might help you. But this will work only for nil and Optional(nil). If optional will have any value then it will not work.

func isOptional(_ instance: Any?) -> Bool {
  if instance == nil { return true }
  if let ins = instance {
    let mirror = Mirror(reflecting: ins)
    let style = mirror.displayStyle
    return style == .optional
  }
  return false
}

And the results will be:

let a: Int = 1  // "1\n"
let b: Int? = 2 "Optional(2)\n"
let c: Double = 3.0 // "3.0\n"
let d: Double? = 4.0 // "Optional(4.0)\n"
let e: NSString = "Hello" // "Hello\n"
let f: NSString? = "Hello" // "Optional(Hello)\n"
let optionalNil = Foo()["baz"] // "Optional(nil)\n"


isOptional(a) // fasle
isOptional(b) // true - warning
isOptional(c) // false
isOptional(d) // true - warning
isOptional(e) // false
isOptional(f) // false
isOptional(optionalNil) // true
isOptional(nil) // true

Originally answered by Kaz Yoshikawa. I just modified it as per your requirement.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.