Home > front end >  How can I tell if a variable is Optional(nil) in Swift without converting it to a String?
How can I tell if a variable is Optional(nil) in Swift without converting it to a String?

Time:01-21

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)"))")

CodePudding user response:

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
  }
}

CodePudding user response:

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

CodePudding user response:

This can be checked using Optional.none

func isNil(_ value: Any?) -> Bool {
    if case Optional.none = value { return true }

    return false
}

CodePudding user response:

You can convert them to AnyObject then compare with NSNull:

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

CodePudding user response:

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.

  • Related