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

myt*_*thz 51 swift

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

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

let values:[Any] = [int,2,str,"bar"]
Run Code Online (Sandbox Code Playgroud)

我们怎样才能提取的值OptionalAny类型(如果有的话),所以我们可以创建一个通用的打印功能,仅打印出的值.

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

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

printArray(values)
Run Code Online (Sandbox Code Playgroud)

哪个会输出:

value[0] = Optional(1)
value[1] = 2
value[2] = Optional("foo")
value[3] = bar
Run Code Online (Sandbox Code Playgroud)

我们如何更改它以便它只打印基础值,以便在可选时将其展开值?例如:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar
Run Code Online (Sandbox Code Playgroud)

更新进度......

更改参数时它可以工作[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)
Run Code Online (Sandbox Code Playgroud)

这将打印所需的:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar
Run Code Online (Sandbox Code Playgroud)

但是仍然希望看到我们如何解包一个Optional,Any因为这会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)")
}
Run Code Online (Sandbox Code Playgroud)

打印出来:

id = 1
name = Optional("foo")
Run Code Online (Sandbox Code Playgroud)

当我需要时:

id = 1
name = foo
Run Code Online (Sandbox Code Playgroud)

bub*_*uxu 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)]
Run Code Online (Sandbox Code Playgroud)

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

更改NSNull()nilunwrap func的返回值Any?将始终解包任何类型.

  • 对于swift 3你应该改变`如果mi.displayStyle!=.可选{`到`如果mi.displayStyle!= .optional {` (3认同)

thm*_*thm 13

为了节省一些人从答案和评论中拼凑起来,这里有一个答案包括"理智"的方式和一些我认为是Swift 3与Xcode 8.2.1一起改进的方法.

使用反射

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
}
Run Code Online (Sandbox Code Playgroud)

讨论

bubuxu接受的答案无法使用Swift 3进行编译.正如walkline在他的评论中所说,.Optional改为.optional修复此问题(参见SE-0005Swift API设计指南).

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

  • 我发现回归NSNull()怪异.
  • 我认为nil返回类型返回的替代Any?方法也存在问题,因为它将所有内容(包括非可选值)转换为可选值(例如unwrap(any: 42)返回Optional(42)).
  • unwrap(any:)用除了Any值之外的任何东西调用(任何更多任何人?)时,Swift 3编译器警告隐式强制转换Any.

类似的想法适用于Sajjon的回答.

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

在可选项上使用扩展名

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()
}
Run Code Online (Sandbox Code Playgroud)

讨论

这是基本上LopSae的解决方案更新到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))")
}
Run Code Online (Sandbox Code Playgroud)

无论你是否使用unwrap()unwrapUsingProtocol(),都会打印出来

id = 1
name = foo
Run Code Online (Sandbox Code Playgroud)

如果您正在寻找一种整齐排列输出的方法,请参阅是否有办法使用制表符在Swift中均匀分隔描述字符串?


Lop*_*Sae 10

要检查Any变量是否是可选的,协议可以用作无类型可选的方法.

正如它目前不可能(如Swift 2)检查无类型的 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
Run Code Online (Sandbox Code Playgroud)

但是,建议的OptionalProtocol也可用于提供一个通用的接口来访问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
Run Code Online (Sandbox Code Playgroud)

使用提供的方法,可以以非常自然的方式检查和访问选项,而无需不必要的强制转换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)
}
Run Code Online (Sandbox Code Playgroud)

哪个会打印:

12
2
nil
maybe
something
Run Code Online (Sandbox Code Playgroud)


rin*_*aro 7

我认为这是一种bug.

通常,要发现和提取特定类型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
}
Run Code Online (Sandbox Code Playgroud)

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

无论如何,显然你可以做到这一点 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)
Run Code Online (Sandbox Code Playgroud)

输出:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar
Run Code Online (Sandbox Code Playgroud)

增加:小调整版

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)")
    }
}
Run Code Online (Sandbox Code Playgroud)

考虑到功能:

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]))")
    }
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*nce 6

在@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)
}
Run Code Online (Sandbox Code Playgroud)