如何从任何类型解包可选值?

时间:2015-01-16 17:06:09

标签: swift

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

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

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

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

E.g。这个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

我们如何更改它以便它只打印基础值,以便在可选时将其解包? e.g:

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

10 个答案:

答案 0 :(得分:31)

对于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,将解包功能的返回值更改为Any?将始终打开任何类型。

答案 1 :(得分:10)

为了挽救某人从答案和评论中拼凑起来,这里有一个答案,包括&#34;理智&#34;方法和一些我认为是Swift 3改进的Xcode 8.2.1。

使用反射

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="?android:attr/windowBackground"
        android:layout_alignParentBottom="true"
        />
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_above="@id/navigation"
    android:layout_height="match_parent">
    <!--Your code for the fragment here.-->
</RelativeLayout>

</RelativeLayout>

讨论

接受的answer from bubuxu无法使用Swift 3进行编译。 正如walkline在评论中所建议的那样,将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 } 更改为.Optional会修复此问题(请参阅SE-0005Swift API Design Guidelines)。

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

  • 我觉得回归.optional很奇怪。
  • 我认为返回类型为NSNull()的{​​{1}}的替代方法也存在问题,因为它会将所有内容(包括非可选值)转换为可选值 (例如nil返回Any?)。
  • 当用unwrap(any: 42)值以外的任何值调用Optional(42)时(任何人都可以吗?)Swift 3编译器会暗中警告 胁迫unwrap(any:)

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

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

在可选

上使用扩展名
nil

讨论

这是基本上LopSae's solution更新到Swift 3.我还更改了前置条件失败消息并添加了Any

用法

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

无论您使用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()

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

答案 2 :(得分:8)

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

正如它目前不可能(如Swift 2)检查无类型可选它也不可能将无类型选中:

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

答案 3 :(得分:6)

我认为这是一种错误。

通常,要从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

ADDED:小调整版

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

答案 4 :(得分:4)

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

答案 5 :(得分: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)

答案 6 :(得分:1)

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

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上的代码。

enter image description here

答案 7 :(得分:0)

不要太复杂,为什么不:

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
FOO

答案 8 :(得分:0)

基于@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
}

但在使用?? nil时,您需要使用unwrap检查nil,如foo

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

虽然还很整洁!

任何人都有?? nil支票的解决方案?

答案 9 :(得分:-1)

根据Swift 2.0中的使用枚举案例模式 那些可能看起来像这样:

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

输出: 332, 232, 55

相关问题