【问题标题】:How to unwrap an optional value from Any type?如何从任何类型中解开可选值?
【发布时间】:2015-03-15 08:39:47
【问题描述】:

给定一个包含可选值和非可选值的[Any] 数组,例如:

let int:Int? = 1
let str:String? = "foo"

let values:[Any] = [int,2,str,"bar"]

我们如何提取Any 类型(如果有)中的Optional 的值,以便我们可以创建一个只打印出值的通用打印函数。

例如这个 printArray 函数遍历并打印每个元素:

func printArray(values:[Any]) {
    for i in 0..<values.count {
        println("value[\(i)] = \(values[i])")
    }
}

printArray(values)

将输出:

value[0] = Optional(1)
value[1] = 2
value[2] = Optional("foo")
value[3] = bar

我们怎样才能改变它,让它只打印底层值,如果它是可选的,它就会解开这个值?例如:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar

更新进度...

将参数更改为[Any?]时可以工作,例如:

let values:[Any?] = [int,2,str,"bar"]

func printArray(values:[Any?]) {
    for i in 0..<values.count {
        println("value[\(i)] = \(values[i]!)")
    }
}

printArray(values)

将打印所需的内容:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar

但仍然想看看我们如何从Any 解包 Optional,因为这是 MirrorType.value 返回的内容,因此难以提取 Optional 值,例如:

class Person {
    var id:Int = 1
    var name:String?
}

var person = Person()
person.name = "foo"

var mt:MirrorType = reflect(person)
for i in 0 ..< mt.count {
    let (name, pt) = mt[i]
    println("\(name) = \(pt.value)")
}

打印出来:

id = 1
name = Optional("foo")

当我需要时:

id = 1
name = foo

【问题讨论】:

  • 真的只是问题的一个特例,你不能问某事是否是可选的......
  • @matt 是的并提取它的价值,只是想表达问题,以便答案会显示这一点,而不是静态可选展开的示例。
  • 是的,为措辞精美的谜题投票。当然,你不会得到任何答案。 :)
  • 添加!在值[i]之后
  • 到目前为止我已经发布了代码。也许有人会渡过最后一道障碍。

标签: swift


【解决方案1】:

不是一个完整的答案。归结为:

let int:Int? = 1
let str:String? = "foo"

let values:[Any] = [int,2,str,"bar"]
func printArray(values:[Any]) {
  for i in 0..<values.count {
    let v = values[i]
    if _stdlib_demangleName(_stdlib_getTypeName(v)) == "Swift.Optional" {
      println("value[\(i)] = "it's optional: \(v)") // here I'm stuck
    }else {
      println("value[\(i)] = \(values[i])")
    }
  }
}

printArray(values)

【讨论】:

  • 仅供参考:if "\(_stdlib_demangleName(_stdlib_getTypeName(v)))" == "Swift.Optional" {...}
  • 是的,我当然想到了这个。但是,尽管您可以使用 stdlib 方法作弊并以字符串的形式看到某些东西是可选的,但您不能强制转换,因此您不能解包。另外,我不会指望未记录的 stdlib 方法继续存在。这个谜题最好保留为谜题:语言中存在一个漏洞。
  • 我们也可以通过if reflect(values[i]).disposition == MirrorDisposition.Optional { ... } 更改它以避免内部stdlib 方法。 @matt 很高兴现在可以开始工作(如果可能的话?)并在正式 API 可用后进行更改。
  • @mythz 所以这不仅仅是一个国际象棋问题?你真的有一个用例吗?这让我很吃惊。
  • @matt 是的,想创建一个通用的 JSON 序列化库,需要提取 Optional 值进行序列化。
【解决方案2】:

不要让它太复杂,为什么不呢:

let int:Int? = 1
let str:String? = "foo"

let values:[Any?] = [int,2,str,"bar"]

for var i:Int = 0; i < values.count; i++
{
    println("\(values[i]!)")
}

打印出来:

1
2

酒吧

【讨论】:

  • 这只有在你知道会有一个值的情况下才可以,通常情况下不是这样的。
  • 这很有趣。 Any? 的使用实际上是解开任何可选的并将其重新包装为 Any?。这意味着如果您使用let any: Any = Optional.Some("maybe"),那么any.dynamicType 将是Optional&lt;String&gt;。但如果你这样做let maybeAny: Any? = Optional.Some("maybe"),那么maybeAny.dynamicType 将是Optional&lt;protocol&lt;&gt;&gt;
【解决方案3】:

我认为这是一种错误。

一般来说,要从Any 中发现和提取特定类型,使用as 向下转换是唯一受支持的方法。但是:

let int:Int? = 1
let any:Any = int

switch any {
case let val as Optional<Int>: // < [!] cannot downcast from 'Any' to a more optional type 'Optional<Int>'
    print(val)
default:
    break
}

这意味着,没有支持的方式来做到这一点。

不管怎样,显然你可以用reflect做到这一点:

func printArray(values:[Any]) {
    for i in 0..<values.count {
        var val = values[i]

        var ref = reflect(val)
        // while `val` is Optional and has `Some` value
        while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
            // replace `val` with unwrapped value
            val = ref[0].1.value;
            ref = reflect(val)
        }

        println("value[\(i)] = \(val)")
    }
}

let int:Int? = 1
let str:String? = "foo"

let values:[Any] = [int,2,str,"bar"]

printArray(values)

输出:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar

添加:微调版本

func printArray(values:[Any]) {
    for i in 0..<values.count {

        var ref = reflect(values[i])
        // while `val` is Optional and has `Some` value
        while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
            // Drill down to the Mirror of unwrapped value
            ref = ref[0].1
        }
        let val = ref.value

        println("value[\(i)] = \(val)")
    }
}

分解成一个函数:

func unwrapAny(val:Any) -> Any {
    var ref = reflect(val)
    while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
        ref = ref[0].1
    }
    return ref.value
}

func printArray(values:[Any]) {
    for i in 0..<values.count {
        println("value[\(i)] = \(unwrapAny(values[i]))")
    }
}

【讨论】:

  • 在答案中添加了微调版本。
  • 请问可以把它转换成 Swift 3 吗?
【解决方案4】:

对于 Xcode 7 和 Swift 2:

func unwrap(any:Any) -> Any {

    let mi = Mirror(reflecting: any)
    if mi.displayStyle != .Optional {
        return any
    }

    if mi.children.count == 0 { return NSNull() }
    let (_, some) = mi.children.first!
    return some

}


let int:Int? = 1
let str:String? = "foo"
let null:Any? = nil
let values:[Any] = [unwrap(int),2,unwrap(str),"bar", unwrap(null)]

这会给你[1, 2, "foo", "bar", {NSObject}]

NSNull() 更改为 nil 并将 unwrap func 的返回值更改为 Any? 将始终解开任何类型。

【讨论】:

  • 如果将unwrap的返回值改为Any?你总是会得到一个可选的。
  • 适用于 Swift 3。:x
  • 无法在 Swift 3 上运行,在 if mi.displayStyle != .Optional { 行失败,错误为 Binary operator '!=' cannot be applied to operands of type 'Mirror.DisplayStyle?' and '_'
  • 对于 swift 3,您应该将 if mi.displayStyle != .Optional { 更改为 if mi.displayStyle != .optional {
  • 为了更好地理解 swift 中的 Optionals,请查看:ios360degree.com/swift-optional-and-non-optional-variables
【解决方案5】:

检查Any 变量是否是可选的a protocol can be used as a means of a typeless Optional

正如目前(从 Swift 2 开始)无法检查 typeless Optional 一样,也无法将 an 转换为 typeless optional:

let anyType: Any.Type = Optional<String>.self
let anyThing: Any = Optional.Some("string")

anyType is Optional.Type // Causes error
let maybeString = anything as? Optional // Also causes error
// Argument for generic parameter 'Wrapped' could not be inferred

不过,提议的OptionalProtocol 也可用于提供一个generic-less 接口来访问 Optional 值甚至解包它们:

protocol OptionalProtocol {
    func isSome() -> Bool
    func unwrap() -> Any
}

extension Optional : OptionalProtocol {
    func isSome() -> Bool {
        switch self {
            case .None: return false
            case .Some: return true
        }
    }

    func unwrap() -> Any {
        switch self {
            // If a nil is unwrapped it will crash!
            case .None: preconditionFailure("nill unwrap")
            case .Some(let unwrapped): return unwrapped
        }
    }
}

// With this we can check if we have an optional
let maybeString: String? = "maybe"
let justString: String = "just"

maybeString is OptionalProtocol // true
justString is OptionalProtocol  // false

通过提供的方法,可以以非常自然的方式检查和访问选项,而不需要不可能的强制转换为Optional

let values:[Any] = [
    Optional.Some(12),
    2,
    Optional<String>.None, // a "wrapped" nil for completeness
    Optional.Some("maybe"),
    "something"
]

for any in values {
    if let optional = any as? OptionalProtocol {
        if optional.isSome() {
            print(optional.unwrap())
        } else {
            // nil should not be unwrapped!
            print(optional)
        }
        continue
    }

    print(any)
}

将打印的内容:

12
2
nil
maybe
something

【讨论】:

  • 这很聪明。与反射相比,它的表现如何?
【解决方案6】:

根据 Using Enumeration case patterns in Swift 2.0 这些可能看起来像这样:

let pattern :[Int?]  = [nil, 332, 232,nil,55]
for case let number? in pattern {
   print(number)
}

输出: 332, 232, 55

【讨论】:

    【解决方案7】:

    根据@bubuxu 的解决方案,还可以:

    func unwrap<T: Any>(any: T) -> T? {
        let mirror = Mirror(reflecting: any)
        guard mirror.displayStyle == .optional else { return any }
        guard let child = mirror.children.first else { return nil }
        return unwrap(any: child.value) as? T
    }
    

    但是在使用unwrap 时,您需要使用?? nil 检查nil,就像在foo 中所做的那样

    func foo<T>(_ maybeValue: T?) {
        if let value: T = unwrap(any: maybeValue) ?? nil {
            print(value)
        }
    }
    

    不过还是很整洁!

    有人知道?? nil 检查的解决方案吗?

    【讨论】:

      【解决方案8】:

      这个解决方案怎么样,我做了之前答案的通用版本。

      fileprivate func unwrap<T>(value: Any)
        -> (unwraped:T?, isOriginalType:Bool) {
      
        let mirror = Mirror(reflecting: value)
        let isOrgType = mirror.subjectType == Optional<T>.self
        if mirror.displayStyle != .optional {
          return (value as? T, isOrgType)
        }
        guard let firstChild = mirror.children.first else {
          return (nil, isOrgType)
        }
        return (firstChild.value as? T, isOrgType)
      }
      
      let value: [Int]? = [0]
      let value2: [Int]? = nil
      
      let anyValue: Any = value
      let anyValue2: Any = value2
      
      let unwrappedResult:([Int]?, Bool)
        = unwrap(value: anyValue)    // ({[0]}, .1 true)
      let unwrappedResult2:([Int]?, Bool)
        = unwrap(value: anyValue2)  // (nil, .1 true)
      let unwrappedResult3:([UInt]?, Bool)
        = unwrap(value: anyValue)  // (nil, .1 false)
      let unwrappedResult4:([NSNumber]?, Bool)
        = unwrap(value: anyValue)  ({[0]}, .1 false)
      

      以下是 Playground 上的代码。

      【讨论】:

        【解决方案9】:

        为了避免有人从答案和 cmets 中拼凑起来,这里有一个答案,包括“理智”的方式和我认为 Xcode 8.2.1 随附的 Swift 3 的一些改进。

        使用反射

        func unwrap<T>(_ any: T) -> Any
        {
            let mirror = Mirror(reflecting: any)
            guard mirror.displayStyle == .optional, let first = mirror.children.first else {
                return any
            }
            return first.value
        }
        

        讨论

        接受的answer from bubuxu 无法使用 Swift 3 进行编译。 正如 walkline 在他的评论中建议的那样,将 .Optional 更改为 .optional 可以解决此问题(请参阅 SE-0005Swift API Design Guidelines)。

        我认为这个解决方案可以改进的原因:

        • 我发现返回 NSNull() 很奇怪。
        • 我认为以Any? 的返回类型返回nil 的替代方法也是有问题的,因为它将所有内容(包括非可选值)都转换为可选值 (例如,unwrap(any: 42) 返回 Optional(42))。
        • 当调用unwrap(any:) 时使用除Any 值以外的任何值(还有任何人吗?),Swift 3 编译器会隐式警告 强制转换为 Any

        类似的想法也适用于Sajjon's answer

        我建议的解决方案解决了所有这些问题。但是请注意 unwrap(_:) 返回 nil 作为类型 Any 所以使用 nil 合并运算符不再起作用。这意味着这只是围绕我认为关于第二点的问题而改变。但我发现这对于(对我而言)更有趣的反射用例来说是正确的做法。

        在 Optional 上使用扩展

        protocol OptionalProtocol {
            func isSome() -> Bool
            func unwrap() -> Any
        }
        
        extension Optional : OptionalProtocol {
            func isSome() -> Bool {
                switch self {
                case .none: return false
                case .some: return true
                }
            }
        
            func unwrap() -> Any {
                switch self {
                case .none: preconditionFailure("trying to unwrap nil")
                case .some(let unwrapped): return unwrapped
                }
            }
        }
        
        func unwrapUsingProtocol<T>(_ any: T) -> Any
        {
            guard let optional = any as? OptionalProtocol, optional.isSome() else {
                return any
            }
            return optional.unwrap()
        }
        

        讨论

        这基本上是 LopSae's solution 更新到 Swift 3。我还更改了前置条件失败消息并添加了 unwrapUsingProtocol(_:)

        用法

        class Person {
            var id:Int = 1
            var name:String?
        }
        
        var person = Person()
        person.name = "foo"
        
        let mirror = Mirror(reflecting: person)
        for child in mirror.children.filter({ $0.label != nil }) {
            print("\(child.label!) = \(unwrap(child.value))")
        }
        

        无论您使用的是unwrap() 还是unwrapUsingProtocol(),都会打印出来

        id = 1
        name = foo
        

        如果您正在寻找一种整齐对齐输出的方法,请参阅Is there a way to use tabs to evenly space out description strings in Swift?

        【讨论】:

          【解决方案10】:

          对@thm 稍作改动以完全展开:

          func unwrap<T>(_ any: T) -> Any {
              let mirror = Mirror(reflecting: any)
              guard mirror.displayStyle == .optional, let first = mirror.children.first else {
                  return any
              }
              return unwrap(first.value)
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-02-06
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多